Mon, 01 Feb 2016 20:03:22 +0100
Started with the web browser page.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/SearchWidget.py Mon Feb 01 20:03:22 2016 +0100 @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the search bar for the web browser. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSlot, Qt +from PyQt5.QtGui import QPalette, QBrush, QColor +from PyQt5.QtWidgets import QWidget +##from PyQt5.QtWebKitWidgets import QWebPage + +from .Ui_SearchWidget import Ui_SearchWidget + +import UI.PixmapCache + + +class SearchWidget(QWidget, Ui_SearchWidget): + """ + Class implementing the search bar for the web browser. + """ + def __init__(self, mainWindow, parent=None): + """ + Constructor + + @param mainWindow reference to the main window (QMainWindow) + @param parent parent widget of this dialog (QWidget) + """ + super(SearchWidget, self).__init__(parent) + self.setupUi(self) + + self.__mainWindow = mainWindow + + # TODO: Check what Qt 5.6 can offer (maybe using JavaScript) + self.wrapCheckBox.setVisible(False) + self.highlightAllCheckBox.setVisible(False) +## self.wrapCheckBox.setChecked(True) + self.closeButton.setIcon(UI.PixmapCache.getIcon("close.png")) + self.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow.png")) + self.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow.png")) + + self.__defaultBaseColor = \ + self.findtextCombo.lineEdit().palette().color(QPalette.Base) + self.__defaultTextColor = \ + self.findtextCombo.lineEdit().palette().color(QPalette.Text) + + self.__findHistory = [] + self.__havefound = False + self.__findBackwards = False + + self.findtextCombo.setCompleter(None) + self.findtextCombo.lineEdit().returnPressed.connect( + self.__findByReturnPressed) + self.findtextCombo.lineEdit().textEdited.connect( + self.__searchTextEdited) + + def on_findtextCombo_editTextChanged(self, txt): + """ + Private slot to enable/disable the find buttons. + + @param txt text of the combobox (string) + """ + self.findPrevButton.setEnabled(txt != "") + self.findNextButton.setEnabled(txt != "") + + def __searchTextEdited(self, txt): + """ + Private slot to perform an incremental search. + + @param txt current text of the search combos line edit (string) + (unused) + """ + self.on_highlightAllCheckBox_toggled(True) + self.__findNextPrev() + + def __findNextPrev(self): + """ + Private slot to find the next occurrence of text. + """ + self.infoLabel.clear() + self.__setFindtextComboBackground(False) + + if not self.findtextCombo.currentText(): + return + + # TODO: adjust this to the browser API + # TODO: use the callback interface of QWebEnginePage + if not self.__mainWindow.currentBrowser().findNextPrev( + self.findtextCombo.currentText(), + self.caseCheckBox.isChecked(), + self.__findBackwards, + self.wrapCheckBox.isChecked(), + False): + self.infoLabel.setText(self.tr("Expression was not found.")) + self.__setFindtextComboBackground(True) +## +## @pyqtSlot(bool) +## def on_highlightAllCheckBox_toggled(self, checked): +## """ +## Private slot to toggle the highlight of all occurrences. +## +## @param checked flag indicating the check box toggle state (boolean) +## """ +## cbr = self.__mainWindow.currentBrowser() +## if cbr is None: +## return +## cbr.findNextPrev( +## "", False, False, False, True) +## if self.highlightAllCheckBox.isChecked(): +## cbr.findNextPrev( +## self.findtextCombo.currentText(), +## self.caseCheckBox.isChecked(), +## False, False, True) + + @pyqtSlot() + def on_findNextButton_clicked(self): + """ + Private slot to find the next occurrence. + """ + txt = self.findtextCombo.currentText() + + # This moves any previous occurrence of this statement to the head + # of the list and updates the combobox + if txt in self.__findHistory: + self.__findHistory.remove(txt) + self.__findHistory.insert(0, txt) + self.findtextCombo.clear() + self.findtextCombo.addItems(self.__findHistory) + + self.__findBackwards = False + self.__findNextPrev() + + def findNext(self): + """ + Public slot to find the next occurrence. + """ + if not self.__havefound or not self.findtextCombo.currentText(): + self.showFind() + return + + self.on_findNextButton_clicked() + + @pyqtSlot() + def on_findPrevButton_clicked(self): + """ + Private slot to find the previous occurrence. + """ + txt = self.findtextCombo.currentText() + + # This moves any previous occurrence of this statement to the head + # of the list and updates the combobox + if txt in self.__findHistory: + self.__findHistory.remove(txt) + self.__findHistory.insert(0, txt) + self.findtextCombo.clear() + self.findtextCombo.addItems(self.__findHistory) + + self.__findBackwards = True + self.__findNextPrev() + + def findPrevious(self): + """ + Public slot to find the previous occurrence. + """ + if not self.__havefound or not self.findtextCombo.currentText(): + self.showFind() + return + + self.on_findPrevButton_clicked() + + def __findByReturnPressed(self): + """ + Private slot to handle the returnPressed signal of the findtext + combobox. + """ + if self.__findBackwards: + self.on_findPrevButton_clicked() + else: + self.on_findNextButton_clicked() + + def showFind(self): + """ + Public method to display this dialog. + """ + self.__havefound = True + self.__findBackwards = False + + self.findtextCombo.clear() + self.findtextCombo.addItems(self.__findHistory) + self.findtextCombo.setEditText('') + self.findtextCombo.setFocus() + + self.caseCheckBox.setChecked(False) + + if self.__mainWindow.currentBrowser().hasSelection(): + self.findtextCombo.setEditText( + self.__mainWindow.currentBrowser().selectedText()) + + self.__setFindtextComboBackground(False) + self.show() + + @pyqtSlot() + def on_closeButton_clicked(self): + """ + Private slot to close the widget. + """ + self.close() + + def keyPressEvent(self, event): + """ + Protected slot to handle key press events. + + @param event reference to the key press event (QKeyEvent) + """ + if event.key() == Qt.Key_Escape: + cb = self.__mainWindow.currentBrowser() + if cb: + cb.setFocus(Qt.ActiveWindowFocusReason) + event.accept() + self.close() + + def __setFindtextComboBackground(self, error): + """ + Private slot to change the findtext combo background to indicate + errors. + + @param error flag indicating an error condition (boolean) + """ + le = self.findtextCombo.lineEdit() + p = le.palette() + if error: + p.setBrush(QPalette.Base, QBrush(QColor("#FF6666"))) + p.setBrush(QPalette.Text, QBrush(QColor("#000000"))) + else: + p.setBrush(QPalette.Base, self.__defaultBaseColor) + p.setBrush(QPalette.Text, self.__defaultTextColor) + le.setPalette(p) + le.update()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/SearchWidget.ui Mon Feb 01 20:03:22 2016 +0100 @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SearchWidget</class> + <widget class="QWidget" name="SearchWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>718</width> + <height>25</height> + </rect> + </property> + <property name="windowTitle"> + <string>Find</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QToolButton" name="closeButton"> + <property name="toolTip"> + <string>Press to close the window</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Find:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="findtextCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="editable"> + <bool>true</bool> + </property> + <property name="insertPolicy"> + <enum>QComboBox::InsertAtTop</enum> + </property> + <property name="duplicatesEnabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="findPrevButton"> + <property name="toolTip"> + <string>Press to find the previous occurrence</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="findNextButton"> + <property name="toolTip"> + <string>Press to find the next occurrence</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="caseCheckBox"> + <property name="text"> + <string>Match case</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="wrapCheckBox"> + <property name="text"> + <string>Wrap around</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="highlightAllCheckBox"> + <property name="text"> + <string>Highlight all</string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="infoLine"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="infoLabel"> + <property name="minimumSize"> + <size> + <width>200</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>findtextCombo</tabstop> + <tabstop>caseCheckBox</tabstop> + <tabstop>wrapCheckBox</tabstop> + <tabstop>highlightAllCheckBox</tabstop> + <tabstop>findNextButton</tabstop> + <tabstop>findPrevButton</tabstop> + <tabstop>closeButton</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/WebBrowserPage.py Mon Feb 01 20:03:22 2016 +0100 @@ -0,0 +1,716 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2008 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + + +""" +Module implementing the helpbrowser using QWebView. +""" + +from __future__ import unicode_literals +try: + str = unicode +except NameError: + pass + +from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QT_TRANSLATE_NOOP, \ + QUrl, QBuffer, QIODevice, QFileInfo, Qt, QTimer, QEvent, \ + QRect, QFile, QPoint, QByteArray, qVersion +from PyQt5.QtGui import QDesktopServices, QClipboard, QMouseEvent, QColor, \ + QPalette +from PyQt5.QtWidgets import qApp, QStyle, QMenu, QApplication, QInputDialog, \ + QLineEdit, QLabel, QToolTip, QFrame, QDialog +from PyQt5.QtPrintSupport import QPrinter, QPrintDialog +##from PyQt5.QtWebKit import QWebSettings +##from PyQt5.QtWebKitWidgets import QWebView, QWebPage +from PyQt5.QtWebEngineWidgets import QWebEnginePage +try: + from PyQt5.QtWebKit import QWebElement +except ImportError: + pass +from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest +import sip + +from E5Gui import E5MessageBox, E5FileDialog + +import WebBrowser +import WebBrowser.WebBrowserWindow + +import Preferences +import UI.PixmapCache +import Globals + +try: + from PyQt5.QtNetwork import QSslCertificate + SSL_AVAILABLE = True +except ImportError: + SSL_AVAILABLE = False + +############################################################################### +## +## +##class JavaScriptExternalObject(QObject): +## """ +## Class implementing an external javascript object to add search providers. +## """ +## def __init__(self, mw, parent=None): +## """ +## Constructor +## +## @param mw reference to the main window 8HelpWindow) +## @param parent reference to the parent object (QObject) +## """ +## super(JavaScriptExternalObject, self).__init__(parent) +## +## self.__mw = mw +## +## @pyqtSlot(str) +## def AddSearchProvider(self, url): +## """ +## Public slot to add a search provider. +## +## @param url url of the XML file defining the search provider (string) +## """ +## self.__mw.openSearchManager().addEngine(QUrl(url)) +## +## +##class LinkedResource(object): +## """ +## Class defining a data structure for linked resources. +## """ +## def __init__(self): +## """ +## Constructor +## """ +## self.rel = "" +## self.type_ = "" +## self.href = "" +## self.title = "" +## +############################################################################### +## +## +##class JavaScriptEricObject(QObject): +## """ +## Class implementing an external javascript object to search via the +## startpage. +## """ +## # these must be in line with the strings used by the javascript part of +## # the start page +## translations = [ +## QT_TRANSLATE_NOOP("JavaScriptEricObject", +## "Welcome to eric6 Web Browser!"), +## QT_TRANSLATE_NOOP("JavaScriptEricObject", "eric6 Web Browser"), +## QT_TRANSLATE_NOOP("JavaScriptEricObject", "Search!"), +## QT_TRANSLATE_NOOP("JavaScriptEricObject", "About eric6"), +## ] +## +## def __init__(self, mw, parent=None): +## """ +## Constructor +## +## @param mw reference to the main window 8HelpWindow) +## @param parent reference to the parent object (QObject) +## """ +## super(JavaScriptEricObject, self).__init__(parent) +## +## self.__mw = mw +## +## @pyqtSlot(str, result=str) +## def translate(self, trans): +## """ +## Public method to translate the given string. +## +## @param trans string to be translated (string) +## @return translation (string) +## """ +## if trans == "QT_LAYOUT_DIRECTION": +## # special handling to detect layout direction +## if qApp.isLeftToRight(): +## return "LTR" +## else: +## return "RTL" +## +## return self.tr(trans) +## +## @pyqtSlot(result=str) +## def providerString(self): +## """ +## Public method to get a string for the search provider. +## +## @return string for the search provider (string) +## """ +## return self.tr("Search results provided by {0}")\ +## .format(self.__mw.openSearchManager().currentEngineName()) +## +## @pyqtSlot(str, result=str) +## def searchUrl(self, searchStr): +## """ +## Public method to get the search URL for the given search term. +## +## @param searchStr search term (string) +## @return search URL (string) +## """ +## return bytes( +## self.__mw.openSearchManager().currentEngine() +## .searchUrl(searchStr).toEncoded()).decode() +## +############################################################################### + + +class WebBrowserPage(QWebEnginePage): + """ + Class implementing an enhanced web page. + """ +## _webPluginFactory = None +## + def __init__(self, parent=None): + """ + Constructor + + @param parent parent widget of this window (QWidget) + """ + super(WebBrowserPage, self).__init__(parent) + +## self.setPluginFactory(self.webPluginFactory()) +## +## self.__lastRequest = None +## self.__lastRequestType = QWebPage.NavigationTypeOther +## +## import Helpviewer.HelpWindow +## from .Network.NetworkAccessManagerProxy import \ +## NetworkAccessManagerProxy +## self.__proxy = NetworkAccessManagerProxy(self) +## self.__proxy.setWebPage(self) +## self.__proxy.setPrimaryNetworkAccessManager( +## Helpviewer.HelpWindow.HelpWindow.networkAccessManager()) +## self.setNetworkAccessManager(self.__proxy) +## +## self.__sslConfiguration = None +## self.__proxy.finished.connect(self.__managerFinished) +## + self.__adBlockedEntries = [] + self.loadStarted.connect(self.__loadStarted) +## +## self.saveFrameStateRequested.connect( +## self.__saveFrameStateRequested) +## self.restoreFrameStateRequested.connect( +## self.__restoreFrameStateRequested) + self.featurePermissionRequested.connect( + self.__featurePermissionRequested) + + def acceptNavigationRequest(self, url, type_, isMainFrame): + """ + Protected method to determine, if a request may be accepted. + + @param url URL to navigate to + @type QUrl + @param type_ type of the navigation request + @type QWebEnginePage.NavigationType + @param isMainFrame flag indicating, that the request originated from + the main frame + @type bool + @return flag indicating acceptance + @rtype bool + """ +## self.__lastRequest = request +## if self.__lastRequest.url() != request.url() or \ +## type_ != QWebPage.NavigationTypeOther: +## self.__lastRequestType = type_ + + # TODO: Qt 5.6: move to handleUnknownProtocol + scheme = url.scheme() + if scheme == "mailto": + QDesktopServices.openUrl(url) + return False + + # AdBlock + if url.scheme() == "abp": + if WebBrowser.WebBrowserWindow.WebBrowserWindow.adBlockManager()\ + .addSubscriptionFromUrl(url): + return False +## +## if type_ == QWebPage.NavigationTypeFormResubmitted: +## res = E5MessageBox.yesNo( +## self.view(), +## self.tr("Resending POST request"), +## self.tr( +## """In order to display the site, the request along with""" +## """ all the data must be sent once again, which may lead""" +## """ to some unexpected behaviour of the site e.g. the""" +## """ same action might be performed once again. Do you""" +## """ want to continue anyway?"""), +## icon=E5MessageBox.Warning) +## if not res: +## return False + + return QWebEnginePage.acceptNavigationRequest(self, url, type_, + isMainFrame) +## +## def populateNetworkRequest(self, request): +## """ +## Public method to add data to a network request. +## +## @param request reference to the network request object +## (QNetworkRequest) +## """ +## try: +## request.setAttribute(QNetworkRequest.User + 100, self) +## if self.__lastRequest.url() == request.url(): +## request.setAttribute(QNetworkRequest.User + 101, +## self.__lastRequestType) +## if self.__lastRequestType == \ +## QWebPage.NavigationTypeLinkClicked: +## request.setRawHeader(b"X-Eric6-UserLoadAction", +## QByteArray(b"1")) +## except TypeError: +## pass +## +## def pageAttributeId(self): +## """ +## Public method to get the attribute id of the page attribute. +## +## @return attribute id of the page attribute (integer) +## """ +## return QNetworkRequest.User + 100 +## +## def supportsExtension(self, extension): +## """ +## Public method to check the support for an extension. +## +## @param extension extension to test for (QWebPage.Extension) +## @return flag indicating the support of extension (boolean) +## """ +## try: +## if extension in [QWebPage.ErrorPageExtension, +## QWebPage.ChooseMultipleFilesExtension]: +## return True +## except AttributeError: +## pass +## +## return QWebPage.supportsExtension(self, extension) +## +## def extension(self, extension, option, output): +## """ +## Public method to implement a specific extension. +## +## @param extension extension to be executed (QWebPage.Extension) +## @param option provides input to the extension +## (QWebPage.ExtensionOption) +## @param output stores the output results (QWebPage.ExtensionReturn) +## @return flag indicating a successful call of the extension (boolean) +## """ +## if extension == QWebPage.ChooseMultipleFilesExtension: +## info = sip.cast(option, +## QWebPage.ChooseMultipleFilesExtensionOption) +## files = sip.cast(output, +## QWebPage.ChooseMultipleFilesExtensionReturn) +## if info is None or files is None: +## return super(HelpWebPage, self).extension( +## extension, option, output) +## +## suggestedFileName = "" +## if info.suggestedFileNames: +## suggestedFileName = info.suggestedFileNames[0] +## +## files.fileNames = E5FileDialog.getOpenFileNames( +## None, +## self.tr("Select files to upload..."), +## suggestedFileName) +## return True +## +## if extension == QWebPage.ErrorPageExtension: +## info = sip.cast(option, QWebPage.ErrorPageExtensionOption) +## +## errorPage = sip.cast(output, QWebPage.ErrorPageExtensionReturn) +## urlString = bytes(info.url.toEncoded()).decode() +## errorPage.baseUrl = info.url +## if info.domain == QWebPage.QtNetwork and \ +## info.error == QNetworkReply.ProtocolUnknownError: +## url = QUrl(info.url) +## res = E5MessageBox.yesNo( +## None, +## self.tr("Protocol Error"), +## self.tr("""Open external application for {0}-link?\n""" +## """URL: {1}""").format( +## url.scheme(), url.toString( +## QUrl.PrettyDecoded | QUrl.RemovePassword)), +## yesDefault=True) +## +## if res: +## QDesktopServices.openUrl(url) +## return True +## elif info.domain == QWebPage.QtNetwork and \ +## info.error == QNetworkReply.ContentAccessDenied and \ +## info.errorString.startswith("AdBlockRule:"): +## if info.frame != info.frame.page().mainFrame(): +## # content in <iframe> +## docElement = info.frame.page().mainFrame()\ +## .documentElement() +## for element in docElement.findAll("iframe"): +## src = element.attribute("src") +## if src in info.url.toString(): +## element.setAttribute("style", "display:none;") +## return False +## else: +## # the whole page is blocked +## rule = info.errorString.replace("AdBlockRule:", "") +## title = self.tr("Content blocked by AdBlock Plus") +## message = self.tr( +## "Blocked by rule: <i>{0}</i>").format(rule) +## +## htmlFile = QFile(":/html/adblockPage.html") +## htmlFile.open(QFile.ReadOnly) +## html = htmlFile.readAll() +## html = html.replace( +## "@FAVICON@", "qrc:icons/adBlockPlus16.png") +## html = html.replace( +## "@IMAGE@", "qrc:icons/adBlockPlus64.png") +## html = html.replace("@TITLE@", title.encode("utf8")) +## html = html.replace("@MESSAGE@", message.encode("utf8")) +## errorPage.content = html +## return True +## +## if info.domain == QWebPage.QtNetwork and \ +## info.error == QNetworkReply.OperationCanceledError and \ +## info.errorString == "eric6:No Error": +## return False +## +## if info.domain == QWebPage.WebKit and info.error == 203: +## # "Loading is handled by the media engine" +## return False +## +## title = self.tr("Error loading page: {0}").format(urlString) +## htmlFile = QFile(":/html/notFoundPage.html") +## htmlFile.open(QFile.ReadOnly) +## html = htmlFile.readAll() +## pixmap = qApp.style()\ +## .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(48, 48) +## imageBuffer = QBuffer() +## imageBuffer.open(QIODevice.ReadWrite) +## if pixmap.save(imageBuffer, "PNG"): +## html = html.replace("@IMAGE@", imageBuffer.buffer().toBase64()) +## pixmap = qApp.style()\ +## .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(16, 16) +## imageBuffer = QBuffer() +## imageBuffer.open(QIODevice.ReadWrite) +## if pixmap.save(imageBuffer, "PNG"): +## html = html.replace( +## "@FAVICON@", imageBuffer.buffer().toBase64()) +## html = html.replace("@TITLE@", title.encode("utf8")) +## html = html.replace("@H1@", info.errorString.encode("utf8")) +## html = html.replace( +## "@H2@", self.tr("When connecting to: {0}.") +## .format(urlString).encode("utf8")) +## html = html.replace( +## "@LI-1@", +## self.tr("Check the address for errors such as " +## "<b>ww</b>.example.org instead of " +## "<b>www</b>.example.org").encode("utf8")) +## html = html.replace( +## "@LI-2@", +## self.tr( +## "If the address is correct, try checking the network " +## "connection.").encode("utf8")) +## html = html.replace( +## "@LI-3@", +## self.tr( +## "If your computer or network is protected by a firewall " +## "or proxy, make sure that the browser is permitted to " +## "access the network.").encode("utf8")) +## html = html.replace( +## "@LI-4@", +## self.tr("If your cache policy is set to offline browsing," +## "only pages in the local cache are available.") +## .encode("utf8")) +## html = html.replace( +## "@BUTTON@", self.tr("Try Again").encode("utf8")) +## errorPage.content = html +## return True +## +## return QWebPage.extension(self, extension, option, output) + + def __loadStarted(self): + """ + Private method to handle the loadStarted signal. + """ + self.__adBlockedEntries = [] +## +## def addAdBlockRule(self, rule, url): +## """ +## Public slot to add an AdBlock rule to the page. +## +## @param rule AdBlock rule to add (AdBlockRule) +## @param url URL that matched the rule (QUrl) +## """ +## from .AdBlock.AdBlockPage import AdBlockedPageEntry +## entry = AdBlockedPageEntry(rule, url) +## if entry not in self.__adBlockedEntries: +## self.__adBlockedEntries.append(entry) +## +## def getAdBlockedPageEntries(self): +## """ +## Public method to get the list of AdBlock page entries. +## +## @return list of AdBlock page entries (list of AdBlockedPageEntry) +## """ +## return self.__adBlockedEntries + + # TODO: re-enable once User Agent Manager is done +## def userAgent(self, resolveEmpty=False): +## """ +## Public method to get the global user agent setting. +## +## @param resolveEmpty flag indicating to resolve an empty +## user agent (boolean) +## @return user agent string (string) +## """ +## agent = Preferences.getWebBrowser("UserAgent") +## if agent == "" and resolveEmpty: +## agent = self.userAgentForUrl(QUrl()) +## return agent +## +## def setUserAgent(self, agent): +## """ +## Public method to set the global user agent string. +## +## @param agent new current user agent string (string) +## """ +## Preferences.setHelp("UserAgent", agent) +## +## def userAgentForUrl(self, url): +## """ +## Public method to determine the user agent for the given URL. +## +## @param url URL to determine user agent for (QUrl) +## @return user agent string (string) +## """ +## import Helpviewer.HelpWindow +## agent = Helpviewer.HelpWindow.HelpWindow.userAgentsManager()\ +## .userAgentForUrl(url) +## if agent == "": +## # no agent string specified for the given host -> use global one +## agent = Preferences.getHelp("UserAgent") +## if agent == "": +## # no global agent string specified -> use default one +## agent = QWebPage.userAgentForUrl(self, url) +## return agent +## +## def __managerFinished(self, reply): +## """ +## Private slot to handle a finished reply. +## +## This slot is used to get SSL related information for a reply. +## +## @param reply reference to the finished reply (QNetworkReply) +## """ +## try: +## frame = reply.request().originatingObject() +## except AttributeError: +## frame = None +## +## mainFrameRequest = frame == self.mainFrame() +## +## if mainFrameRequest and \ +## self.__sslConfiguration is not None and \ +## reply.url() == self.mainFrame().url(): +## self.__sslConfiguration = None +## +## if reply.error() == QNetworkReply.NoError and \ +## mainFrameRequest and \ +## self.__sslConfiguration is None and \ +## reply.url().scheme().lower() == "https" and \ +## reply.url() == self.mainFrame().url(): +## self.__sslConfiguration = reply.sslConfiguration() +## self.__sslConfiguration.url = QUrl(reply.url()) +## +## if reply.error() == QNetworkReply.NoError and \ +## mainFrameRequest and \ +## reply.url() == self.mainFrame().url(): +## modified = reply.header(QNetworkRequest.LastModifiedHeader) +## if modified and modified.isValid(): +## import Helpviewer.HelpWindow +## manager = Helpviewer.HelpWindow.HelpWindow.bookmarksManager() +## from .Bookmarks.BookmarkNode import BookmarkNode +## for bookmark in manager.bookmarksForUrl(reply.url()): +## manager.setTimestamp(bookmark, BookmarkNode.TsModified, +## modified) + +## def getSslCertificate(self): +## """ +## Public method to get a reference to the SSL certificate. +## +## @return amended SSL certificate (QSslCertificate) +## """ +## if self.__sslConfiguration is None: +## return None +## +## sslInfo = self.__sslConfiguration.peerCertificate() +## sslInfo.url = QUrl(self.__sslConfiguration.url) +## return sslInfo +## +## def getSslCertificateChain(self): +## """ +## Public method to get a reference to the SSL certificate chain. +## +## @return SSL certificate chain (list of QSslCertificate) +## """ +## if self.__sslConfiguration is None: +## return [] +## +## chain = self.__sslConfiguration.peerCertificateChain() +## return chain +## +## def getSslConfiguration(self): +## """ +## Public method to return a reference to the current SSL configuration. +## +## @return reference to the SSL configuration in use (QSslConfiguration) +## """ +## return self.__sslConfiguration +## +## def showSslInfo(self, pos): +## """ +## Public slot to show some SSL information for the loaded page. +## +## @param pos position to show the info at (QPoint) +## """ +## if SSL_AVAILABLE and self.__sslConfiguration is not None: +## from E5Network.E5SslInfoWidget import E5SslInfoWidget +## widget = E5SslInfoWidget( +## self.mainFrame().url(), self.__sslConfiguration, self.view()) +## widget.showAt(pos) +## else: +## E5MessageBox.warning( +## self.view(), +## self.tr("SSL Info"), +## self.tr("""This site does not contain SSL information.""")) +## +## def hasValidSslInfo(self): +## """ +## Public method to check, if the page has a valid SSL certificate. +## +## @return flag indicating a valid SSL certificate (boolean) +## """ +## if self.__sslConfiguration is None: +## return False +## +## certList = self.__sslConfiguration.peerCertificateChain() +## if not certList: +## return False +## +## certificateDict = Globals.toDict( +## Preferences.Prefs.settings.value("Ssl/CaCertificatesDict")) +## for server in certificateDict: +## localCAList = QSslCertificate.fromData(certificateDict[server]) +## for cert in certList: +## if cert in localCAList: +## return True +## +## if qVersion() >= "5.0.0": +## for cert in certList: +## if cert.isBlacklisted(): +## return False +## else: +## for cert in certList: +## if not cert.isValid(): +## return False +## +## return True + +## @classmethod +## def webPluginFactory(cls): +## """ +## Class method to get a reference to the web plug-in factory +## instance. +## +## @return reference to the web plug-in factory instance (WebPluginFactory +## """ +## if cls._webPluginFactory is None: +## from .WebPlugins.WebPluginFactory import WebPluginFactory +## cls._webPluginFactory = WebPluginFactory() +## +## return cls._webPluginFactory +## +## def event(self, evt): +## """ +## Public method implementing the event handler. +## +## @param evt reference to the event (QEvent) +## @return flag indicating that the event was handled (boolean) +## """ +## if evt.type() == QEvent.Leave: +## # Fake a mouse move event just outside of the widget to trigger +## # the WebKit event handler's mouseMoved function. This implements +## # the interesting mouse-out behavior like invalidating scrollbars. +## fakeEvent = QMouseEvent(QEvent.MouseMove, QPoint(0, -1), +## Qt.NoButton, Qt.NoButton, Qt.NoModifier) +## return super(HelpWebPage, self).event(fakeEvent) +## +## return super(HelpWebPage, self).event(evt) +## +## def __saveFrameStateRequested(self, frame, itm): +## """ +## Private slot to save the page state (i.e. zoom level and scroll +## position). +## +## Note: Code is based on qutebrowser. +## +## @param frame frame to be saved +## @type QWebFrame +## @param itm web history item to be saved +## @type QWebHistoryItem +## """ +## try: +## if frame != self.mainFrame(): +## return +## except RuntimeError: +## # With Qt 5.2.1 (Ubuntu Trusty) we get this when closing a tab: +## # RuntimeError: wrapped C/C++ object of type BrowserPage has +## # been deleted +## # Since the information here isn't that important for closing web +## # views anyways, we ignore this error. +## return +## data = { +## 'zoom': frame.zoomFactor(), +## 'scrollPos': frame.scrollPosition(), +## } +## itm.setUserData(data) +## +## def __restoreFrameStateRequested(self, frame): +## """ +## Private slot to restore scroll position and zoom level from +## history. +## +## Note: Code is based on qutebrowser. +## +## @param frame frame to be restored +## @type QWebFrame +## """ +## if frame != self.mainFrame(): +## return +## +## data = self.history().currentItem().userData() +## if data is None: +## return +## +## if 'zoom' in data: +## frame.page().view().setZoomValue(int(data['zoom'] * 100), +## saveValue=False) +## +## if 'scrollPos' in data and frame.scrollPosition() == QPoint(0, 0): +## frame.setScrollPosition(data['scrollPos']) + + def __featurePermissionRequested(self, url, feature): + """ + Private slot handling a feature permission request. + + @param url url requesting the feature + @type QUrl + @param feature requested feature + @type QWebEnginePage.Feature + """ + manager = WebBrowser.WebBrowserWindow.WebBrowserWindow\ + .featurePermissionManager() + manager.requestFeaturePermission(self, frame, feature)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/WebBrowserView.py Mon Feb 01 20:03:22 2016 +0100 @@ -0,0 +1,2629 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2008 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + + +""" +Module implementing the helpbrowser using QWebView. +""" + +from __future__ import unicode_literals +try: + str = unicode +except NameError: + pass + +from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QT_TRANSLATE_NOOP, \ + QUrl, QBuffer, QIODevice, QFileInfo, Qt, QTimer, QEvent, \ + QRect, QFile, QPoint, QByteArray, qVersion +from PyQt5.QtGui import QDesktopServices, QClipboard, QMouseEvent, QColor, \ + QPalette +from PyQt5.QtWidgets import qApp, QStyle, QMenu, QApplication, QInputDialog, \ + QLineEdit, QLabel, QToolTip, QFrame, QDialog +from PyQt5.QtPrintSupport import QPrinter, QPrintDialog +from PyQt5.QtWebKit import QWebSettings +from PyQt5.QtWebKitWidgets import QWebView, QWebPage +try: + from PyQt5.QtWebKit import QWebElement +except ImportError: + pass +from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest +import sip + +from E5Gui import E5MessageBox, E5FileDialog + +import Helpviewer + +import Preferences +import UI.PixmapCache +import Globals + +try: + from PyQt5.QtNetwork import QSslCertificate + SSL_AVAILABLE = True +except ImportError: + SSL_AVAILABLE = False + +############################################################################### + + +class JavaScriptExternalObject(QObject): + """ + Class implementing an external javascript object to add search providers. + """ + def __init__(self, mw, parent=None): + """ + Constructor + + @param mw reference to the main window 8HelpWindow) + @param parent reference to the parent object (QObject) + """ + super(JavaScriptExternalObject, self).__init__(parent) + + self.__mw = mw + + @pyqtSlot(str) + def AddSearchProvider(self, url): + """ + Public slot to add a search provider. + + @param url url of the XML file defining the search provider (string) + """ + self.__mw.openSearchManager().addEngine(QUrl(url)) + + +class LinkedResource(object): + """ + Class defining a data structure for linked resources. + """ + def __init__(self): + """ + Constructor + """ + self.rel = "" + self.type_ = "" + self.href = "" + self.title = "" + +############################################################################### + + +class JavaScriptEricObject(QObject): + """ + Class implementing an external javascript object to search via the + startpage. + """ + # these must be in line with the strings used by the javascript part of + # the start page + translations = [ + QT_TRANSLATE_NOOP("JavaScriptEricObject", + "Welcome to eric6 Web Browser!"), + QT_TRANSLATE_NOOP("JavaScriptEricObject", "eric6 Web Browser"), + QT_TRANSLATE_NOOP("JavaScriptEricObject", "Search!"), + QT_TRANSLATE_NOOP("JavaScriptEricObject", "About eric6"), + ] + + def __init__(self, mw, parent=None): + """ + Constructor + + @param mw reference to the main window 8HelpWindow) + @param parent reference to the parent object (QObject) + """ + super(JavaScriptEricObject, self).__init__(parent) + + self.__mw = mw + + @pyqtSlot(str, result=str) + def translate(self, trans): + """ + Public method to translate the given string. + + @param trans string to be translated (string) + @return translation (string) + """ + if trans == "QT_LAYOUT_DIRECTION": + # special handling to detect layout direction + if qApp.isLeftToRight(): + return "LTR" + else: + return "RTL" + + return self.tr(trans) + + @pyqtSlot(result=str) + def providerString(self): + """ + Public method to get a string for the search provider. + + @return string for the search provider (string) + """ + return self.tr("Search results provided by {0}")\ + .format(self.__mw.openSearchManager().currentEngineName()) + + @pyqtSlot(str, result=str) + def searchUrl(self, searchStr): + """ + Public method to get the search URL for the given search term. + + @param searchStr search term (string) + @return search URL (string) + """ + return bytes( + self.__mw.openSearchManager().currentEngine() + .searchUrl(searchStr).toEncoded()).decode() + +############################################################################### + + +class HelpWebPage(QWebPage): + """ + Class implementing an enhanced web page. + """ + _webPluginFactory = None + + def __init__(self, parent=None): + """ + Constructor + + @param parent parent widget of this window (QWidget) + """ + super(HelpWebPage, self).__init__(parent) + + self.setPluginFactory(self.webPluginFactory()) + + self.__lastRequest = None + self.__lastRequestType = QWebPage.NavigationTypeOther + + import Helpviewer.HelpWindow + from .Network.NetworkAccessManagerProxy import \ + NetworkAccessManagerProxy + self.__proxy = NetworkAccessManagerProxy(self) + self.__proxy.setWebPage(self) + self.__proxy.setPrimaryNetworkAccessManager( + Helpviewer.HelpWindow.HelpWindow.networkAccessManager()) + self.setNetworkAccessManager(self.__proxy) + + self.__sslConfiguration = None + self.__proxy.finished.connect(self.__managerFinished) + + self.__adBlockedEntries = [] + self.loadStarted.connect(self.__loadStarted) + + self.saveFrameStateRequested.connect( + self.__saveFrameStateRequested) + self.restoreFrameStateRequested.connect( + self.__restoreFrameStateRequested) + + def acceptNavigationRequest(self, frame, request, type_): + """ + Public method to determine, if a request may be accepted. + + @param frame reference to the frame sending the request (QWebFrame) + @param request reference to the request object (QNetworkRequest) + @param type_ type of the navigation request (QWebPage.NavigationType) + @return flag indicating acceptance (boolean) + """ + self.__lastRequest = request + if self.__lastRequest.url() != request.url() or \ + type_ != QWebPage.NavigationTypeOther: + self.__lastRequestType = type_ + + scheme = request.url().scheme() + if scheme == "mailto": + QDesktopServices.openUrl(request.url()) + return False + + if type_ == QWebPage.NavigationTypeFormResubmitted: + res = E5MessageBox.yesNo( + self.view(), + self.tr("Resending POST request"), + self.tr( + """In order to display the site, the request along with""" + """ all the data must be sent once again, which may lead""" + """ to some unexpected behaviour of the site e.g. the""" + """ same action might be performed once again. Do you""" + """ want to continue anyway?"""), + icon=E5MessageBox.Warning) + if not res: + return False + + return QWebPage.acceptNavigationRequest(self, frame, request, type_) + + def populateNetworkRequest(self, request): + """ + Public method to add data to a network request. + + @param request reference to the network request object + (QNetworkRequest) + """ + try: + request.setAttribute(QNetworkRequest.User + 100, self) + if self.__lastRequest.url() == request.url(): + request.setAttribute(QNetworkRequest.User + 101, + self.__lastRequestType) + if self.__lastRequestType == \ + QWebPage.NavigationTypeLinkClicked: + request.setRawHeader(b"X-Eric6-UserLoadAction", + QByteArray(b"1")) + except TypeError: + pass + + def pageAttributeId(self): + """ + Public method to get the attribute id of the page attribute. + + @return attribute id of the page attribute (integer) + """ + return QNetworkRequest.User + 100 + + def supportsExtension(self, extension): + """ + Public method to check the support for an extension. + + @param extension extension to test for (QWebPage.Extension) + @return flag indicating the support of extension (boolean) + """ + try: + if extension in [QWebPage.ErrorPageExtension, + QWebPage.ChooseMultipleFilesExtension]: + return True + except AttributeError: + pass + + return QWebPage.supportsExtension(self, extension) + + def extension(self, extension, option, output): + """ + Public method to implement a specific extension. + + @param extension extension to be executed (QWebPage.Extension) + @param option provides input to the extension + (QWebPage.ExtensionOption) + @param output stores the output results (QWebPage.ExtensionReturn) + @return flag indicating a successful call of the extension (boolean) + """ + if extension == QWebPage.ChooseMultipleFilesExtension: + info = sip.cast(option, + QWebPage.ChooseMultipleFilesExtensionOption) + files = sip.cast(output, + QWebPage.ChooseMultipleFilesExtensionReturn) + if info is None or files is None: + return super(HelpWebPage, self).extension( + extension, option, output) + + suggestedFileName = "" + if info.suggestedFileNames: + suggestedFileName = info.suggestedFileNames[0] + + files.fileNames = E5FileDialog.getOpenFileNames( + None, + self.tr("Select files to upload..."), + suggestedFileName) + return True + + if extension == QWebPage.ErrorPageExtension: + info = sip.cast(option, QWebPage.ErrorPageExtensionOption) + + errorPage = sip.cast(output, QWebPage.ErrorPageExtensionReturn) + urlString = bytes(info.url.toEncoded()).decode() + errorPage.baseUrl = info.url + if info.domain == QWebPage.QtNetwork and \ + info.error == QNetworkReply.ProtocolUnknownError: + url = QUrl(info.url) + res = E5MessageBox.yesNo( + None, + self.tr("Protocol Error"), + self.tr("""Open external application for {0}-link?\n""" + """URL: {1}""").format( + url.scheme(), url.toString( + QUrl.PrettyDecoded | QUrl.RemovePassword)), + yesDefault=True) + + if res: + QDesktopServices.openUrl(url) + return True + elif info.domain == QWebPage.QtNetwork and \ + info.error == QNetworkReply.ContentAccessDenied and \ + info.errorString.startswith("AdBlockRule:"): + if info.frame != info.frame.page().mainFrame(): + # content in <iframe> + docElement = info.frame.page().mainFrame()\ + .documentElement() + for element in docElement.findAll("iframe"): + src = element.attribute("src") + if src in info.url.toString(): + element.setAttribute("style", "display:none;") + return False + else: + # the whole page is blocked + rule = info.errorString.replace("AdBlockRule:", "") + title = self.tr("Content blocked by AdBlock Plus") + message = self.tr( + "Blocked by rule: <i>{0}</i>").format(rule) + + htmlFile = QFile(":/html/adblockPage.html") + htmlFile.open(QFile.ReadOnly) + html = htmlFile.readAll() + html = html.replace( + "@FAVICON@", "qrc:icons/adBlockPlus16.png") + html = html.replace( + "@IMAGE@", "qrc:icons/adBlockPlus64.png") + html = html.replace("@TITLE@", title.encode("utf8")) + html = html.replace("@MESSAGE@", message.encode("utf8")) + errorPage.content = html + return True + + if info.domain == QWebPage.QtNetwork and \ + info.error == QNetworkReply.OperationCanceledError and \ + info.errorString == "eric6:No Error": + return False + + if info.domain == QWebPage.WebKit and info.error == 203: + # "Loading is handled by the media engine" + return False + + title = self.tr("Error loading page: {0}").format(urlString) + htmlFile = QFile(":/html/notFoundPage.html") + htmlFile.open(QFile.ReadOnly) + html = htmlFile.readAll() + pixmap = qApp.style()\ + .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(48, 48) + imageBuffer = QBuffer() + imageBuffer.open(QIODevice.ReadWrite) + if pixmap.save(imageBuffer, "PNG"): + html = html.replace("@IMAGE@", imageBuffer.buffer().toBase64()) + pixmap = qApp.style()\ + .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(16, 16) + imageBuffer = QBuffer() + imageBuffer.open(QIODevice.ReadWrite) + if pixmap.save(imageBuffer, "PNG"): + html = html.replace( + "@FAVICON@", imageBuffer.buffer().toBase64()) + html = html.replace("@TITLE@", title.encode("utf8")) + html = html.replace("@H1@", info.errorString.encode("utf8")) + html = html.replace( + "@H2@", self.tr("When connecting to: {0}.") + .format(urlString).encode("utf8")) + html = html.replace( + "@LI-1@", + self.tr("Check the address for errors such as " + "<b>ww</b>.example.org instead of " + "<b>www</b>.example.org").encode("utf8")) + html = html.replace( + "@LI-2@", + self.tr( + "If the address is correct, try checking the network " + "connection.").encode("utf8")) + html = html.replace( + "@LI-3@", + self.tr( + "If your computer or network is protected by a firewall " + "or proxy, make sure that the browser is permitted to " + "access the network.").encode("utf8")) + html = html.replace( + "@LI-4@", + self.tr("If your cache policy is set to offline browsing," + "only pages in the local cache are available.") + .encode("utf8")) + html = html.replace( + "@BUTTON@", self.tr("Try Again").encode("utf8")) + errorPage.content = html + return True + + return QWebPage.extension(self, extension, option, output) + + def __loadStarted(self): + """ + Private method to handle the loadStarted signal. + """ + self.__adBlockedEntries = [] + + def addAdBlockRule(self, rule, url): + """ + Public slot to add an AdBlock rule to the page. + + @param rule AdBlock rule to add (AdBlockRule) + @param url URL that matched the rule (QUrl) + """ + from .AdBlock.AdBlockPage import AdBlockedPageEntry + entry = AdBlockedPageEntry(rule, url) + if entry not in self.__adBlockedEntries: + self.__adBlockedEntries.append(entry) + + def getAdBlockedPageEntries(self): + """ + Public method to get the list of AdBlock page entries. + + @return list of AdBlock page entries (list of AdBlockedPageEntry) + """ + return self.__adBlockedEntries + + def url(self): + """ + Public method to get the URL of the page. + + @return URL of the page (QUrl) + """ + return self.mainFrame().url() + + def userAgent(self, resolveEmpty=False): + """ + Public method to get the global user agent setting. + + @param resolveEmpty flag indicating to resolve an empty + user agent (boolean) + @return user agent string (string) + """ + agent = Preferences.getHelp("UserAgent") + if agent == "" and resolveEmpty: + agent = self.userAgentForUrl(QUrl()) + return agent + + def setUserAgent(self, agent): + """ + Public method to set the global user agent string. + + @param agent new current user agent string (string) + """ + Preferences.setHelp("UserAgent", agent) + + def userAgentForUrl(self, url): + """ + Public method to determine the user agent for the given URL. + + @param url URL to determine user agent for (QUrl) + @return user agent string (string) + """ + import Helpviewer.HelpWindow + agent = Helpviewer.HelpWindow.HelpWindow.userAgentsManager()\ + .userAgentForUrl(url) + if agent == "": + # no agent string specified for the given host -> use global one + agent = Preferences.getHelp("UserAgent") + if agent == "": + # no global agent string specified -> use default one + agent = QWebPage.userAgentForUrl(self, url) + return agent + + def __managerFinished(self, reply): + """ + Private slot to handle a finished reply. + + This slot is used to get SSL related information for a reply. + + @param reply reference to the finished reply (QNetworkReply) + """ + try: + frame = reply.request().originatingObject() + except AttributeError: + frame = None + + mainFrameRequest = frame == self.mainFrame() + + if mainFrameRequest and \ + self.__sslConfiguration is not None and \ + reply.url() == self.mainFrame().url(): + self.__sslConfiguration = None + + if reply.error() == QNetworkReply.NoError and \ + mainFrameRequest and \ + self.__sslConfiguration is None and \ + reply.url().scheme().lower() == "https" and \ + reply.url() == self.mainFrame().url(): + self.__sslConfiguration = reply.sslConfiguration() + self.__sslConfiguration.url = QUrl(reply.url()) + + if reply.error() == QNetworkReply.NoError and \ + mainFrameRequest and \ + reply.url() == self.mainFrame().url(): + modified = reply.header(QNetworkRequest.LastModifiedHeader) + if modified and modified.isValid(): + import Helpviewer.HelpWindow + manager = Helpviewer.HelpWindow.HelpWindow.bookmarksManager() + from .Bookmarks.BookmarkNode import BookmarkNode + for bookmark in manager.bookmarksForUrl(reply.url()): + manager.setTimestamp(bookmark, BookmarkNode.TsModified, + modified) + + def getSslCertificate(self): + """ + Public method to get a reference to the SSL certificate. + + @return amended SSL certificate (QSslCertificate) + """ + if self.__sslConfiguration is None: + return None + + sslInfo = self.__sslConfiguration.peerCertificate() + sslInfo.url = QUrl(self.__sslConfiguration.url) + return sslInfo + + def getSslCertificateChain(self): + """ + Public method to get a reference to the SSL certificate chain. + + @return SSL certificate chain (list of QSslCertificate) + """ + if self.__sslConfiguration is None: + return [] + + chain = self.__sslConfiguration.peerCertificateChain() + return chain + + def getSslConfiguration(self): + """ + Public method to return a reference to the current SSL configuration. + + @return reference to the SSL configuration in use (QSslConfiguration) + """ + return self.__sslConfiguration + + def showSslInfo(self, pos): + """ + Public slot to show some SSL information for the loaded page. + + @param pos position to show the info at (QPoint) + """ + if SSL_AVAILABLE and self.__sslConfiguration is not None: + from E5Network.E5SslInfoWidget import E5SslInfoWidget + widget = E5SslInfoWidget( + self.mainFrame().url(), self.__sslConfiguration, self.view()) + widget.showAt(pos) + else: + E5MessageBox.warning( + self.view(), + self.tr("SSL Info"), + self.tr("""This site does not contain SSL information.""")) + + def hasValidSslInfo(self): + """ + Public method to check, if the page has a valid SSL certificate. + + @return flag indicating a valid SSL certificate (boolean) + """ + if self.__sslConfiguration is None: + return False + + certList = self.__sslConfiguration.peerCertificateChain() + if not certList: + return False + + certificateDict = Globals.toDict( + Preferences.Prefs.settings.value("Ssl/CaCertificatesDict")) + for server in certificateDict: + localCAList = QSslCertificate.fromData(certificateDict[server]) + for cert in certList: + if cert in localCAList: + return True + + if qVersion() >= "5.0.0": + for cert in certList: + if cert.isBlacklisted(): + return False + else: + for cert in certList: + if not cert.isValid(): + return False + + return True + + @classmethod + def webPluginFactory(cls): + """ + Class method to get a reference to the web plug-in factory + instance. + + @return reference to the web plug-in factory instance (WebPluginFactory + """ + if cls._webPluginFactory is None: + from .WebPlugins.WebPluginFactory import WebPluginFactory + cls._webPluginFactory = WebPluginFactory() + + return cls._webPluginFactory + + def event(self, evt): + """ + Public method implementing the event handler. + + @param evt reference to the event (QEvent) + @return flag indicating that the event was handled (boolean) + """ + if evt.type() == QEvent.Leave: + # Fake a mouse move event just outside of the widget to trigger + # the WebKit event handler's mouseMoved function. This implements + # the interesting mouse-out behavior like invalidating scrollbars. + fakeEvent = QMouseEvent(QEvent.MouseMove, QPoint(0, -1), + Qt.NoButton, Qt.NoButton, Qt.NoModifier) + return super(HelpWebPage, self).event(fakeEvent) + + return super(HelpWebPage, self).event(evt) + + def __saveFrameStateRequested(self, frame, itm): + """ + Private slot to save the page state (i.e. zoom level and scroll + position). + + Note: Code is based on qutebrowser. + + @param frame frame to be saved + @type QWebFrame + @param itm web history item to be saved + @type QWebHistoryItem + """ + try: + if frame != self.mainFrame(): + return + except RuntimeError: + # With Qt 5.2.1 (Ubuntu Trusty) we get this when closing a tab: + # RuntimeError: wrapped C/C++ object of type BrowserPage has + # been deleted + # Since the information here isn't that important for closing web + # views anyways, we ignore this error. + return + data = { + 'zoom': frame.zoomFactor(), + 'scrollPos': frame.scrollPosition(), + } + itm.setUserData(data) + + def __restoreFrameStateRequested(self, frame): + """ + Private slot to restore scroll position and zoom level from + history. + + Note: Code is based on qutebrowser. + + @param frame frame to be restored + @type QWebFrame + """ + if frame != self.mainFrame(): + return + + data = self.history().currentItem().userData() + if data is None: + return + + if 'zoom' in data: + frame.page().view().setZoomValue(int(data['zoom'] * 100), + saveValue=False) + + if 'scrollPos' in data and frame.scrollPosition() == QPoint(0, 0): + frame.setScrollPosition(data['scrollPos']) + +############################################################################### + + +class HelpBrowser(QWebView): + """ + Class implementing the helpbrowser widget. + + This is a subclass of the Qt QWebView to implement an + interface compatible with the QTextBrowser based variant. + + @signal sourceChanged(QUrl) emitted after the current URL has changed + @signal forwardAvailable(bool) emitted after the current URL has changed + @signal backwardAvailable(bool) emitted after the current URL has changed + @signal highlighted(str) emitted, when the mouse hovers over a link + @signal search(QUrl) emitted, when a search is requested + @signal zoomValueChanged(int) emitted to signal a change of the zoom value + """ + sourceChanged = pyqtSignal(QUrl) + forwardAvailable = pyqtSignal(bool) + backwardAvailable = pyqtSignal(bool) + highlighted = pyqtSignal(str) + search = pyqtSignal(QUrl) + zoomValueChanged = pyqtSignal(int) + + ZoomLevels = [ + 30, 50, 67, 80, 90, + 100, + 110, 120, 133, 150, 170, 200, 240, 300, + ] + ZoomLevelDefault = 100 + + def __init__(self, mainWindow, parent=None, name=""): + """ + Constructor + + @param mainWindow reference to the main window (HelpWindow) + @param parent parent widget of this window (QWidget) + @param name name of this window (string) + """ + super(HelpBrowser, self).__init__(parent) + self.setObjectName(name) + self.setWhatsThis(self.tr( + """<b>Help Window</b>""" + """<p>This window displays the selected help information.</p>""" + )) + + import Helpviewer.HelpWindow + self.__speedDial = Helpviewer.HelpWindow.HelpWindow.speedDial() + + self.__page = HelpWebPage(self) + self.setPage(self.__page) + + self.mw = mainWindow + self.ctrlPressed = False + self.__isLoading = False + self.__progress = 0 + + self.__currentZoom = 100 + self.__zoomLevels = HelpBrowser.ZoomLevels[:] + + self.__javaScriptBinding = None + self.__javaScriptEricObject = None + + self.mw.zoomTextOnlyChanged.connect(self.__applyZoom) + + self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) + self.linkClicked.connect(self.setSource) + + self.urlChanged.connect(self.__urlChanged) + self.statusBarMessage.connect(self.__statusBarMessage) + self.page().linkHovered.connect(self.__linkHovered) + + self.loadStarted.connect(self.__loadStarted) + self.loadProgress.connect(self.__loadProgress) + self.loadFinished.connect(self.__loadFinished) + + self.page().setForwardUnsupportedContent(True) + self.page().unsupportedContent.connect(self.__unsupportedContent) + + self.page().featurePermissionRequested.connect( + self.__featurePermissionRequested) + + self.page().downloadRequested.connect(self.__downloadRequested) + self.page().frameCreated.connect(self.__addExternalBinding) + self.__addExternalBinding(self.page().mainFrame()) + + self.page().databaseQuotaExceeded.connect(self.__databaseQuotaExceeded) + + self.mw.openSearchManager().currentEngineChanged.connect( + self.__currentEngineChanged) + + self.setAcceptDrops(True) + + self.__enableAccessKeys = Preferences.getHelp("AccessKeysEnabled") + self.__accessKeysPressed = False + self.__accessKeyLabels = [] + self.__accessKeyNodes = {} + + self.page().loadStarted.connect(self.__hideAccessKeys) + self.page().scrollRequested.connect(self.__hideAccessKeys) + + self.__rss = [] + + self.__clickedFrame = None + + self.mw.personalInformationManager().connectPage(self.page()) + self.mw.greaseMonkeyManager().connectPage(self.page()) + + self.__inspector = None + + self.grabGesture(Qt.PinchGesture) + + def __addExternalBinding(self, frame=None): + """ + Private slot to add javascript bindings for adding search providers. + + @param frame reference to the web frame (QWebFrame) + """ + self.page().settings().setAttribute(QWebSettings.JavascriptEnabled, + True) + if self.__javaScriptBinding is None: + self.__javaScriptBinding = JavaScriptExternalObject(self.mw, self) + + if frame is None: + # called from QWebFrame.javaScriptWindowObjectCleared + frame = self.sender() + if isinstance(frame, HelpWebPage): + frame = frame.mainFrame() + if frame.url().scheme() == "eric" and frame.url().path() == "home": + if self.__javaScriptEricObject is None: + self.__javaScriptEricObject = JavaScriptEricObject( + self.mw, self) + frame.addToJavaScriptWindowObject( + "eric", self.__javaScriptEricObject) + elif frame.url().scheme() == "eric" and \ + frame.url().path() == "speeddial": + frame.addToJavaScriptWindowObject( + "speeddial", self.__speedDial) + self.__speedDial.addWebFrame(frame) + else: + # called from QWebPage.frameCreated + frame.javaScriptWindowObjectCleared.connect( + self.__addExternalBinding) + frame.addToJavaScriptWindowObject("external", self.__javaScriptBinding) + + def linkedResources(self, relation=""): + """ + Public method to extract linked resources. + + @param relation relation to extract (string) + @return list of linked resources (list of LinkedResource) + """ + resources = [] + + baseUrl = self.page().mainFrame().baseUrl() + + linkElements = self.page().mainFrame().findAllElements( + "html > head > link") + + for linkElement in linkElements.toList(): + rel = linkElement.attribute("rel") + href = linkElement.attribute("href") + type_ = linkElement.attribute("type") + title = linkElement.attribute("title") + + if href == "" or type_ == "": + continue + if relation and rel != relation: + continue + + resource = LinkedResource() + resource.rel = rel + resource.type_ = type_ + resource.href = baseUrl.resolved( + QUrl.fromEncoded(href.encode("utf-8"))) + resource.title = title + + resources.append(resource) + + return resources + + def __currentEngineChanged(self): + """ + Private slot to track a change of the current search engine. + """ + if self.url().toString() == "eric:home": + self.reload() + + def setSource(self, name, requestData=None): + """ + Public method used to set the source to be displayed. + + @param name filename to be shown (QUrl) + @param requestData tuple containing the request data (QNetworkRequest, + QNetworkAccessManager.Operation, QByteArray) + """ + if (name is None or not name.isValid()) and requestData is None: + return + + if name is None and requestData is not None: + name = requestData[0].url() + + if self.ctrlPressed: + # open in a new window + self.mw.newTab(name) + self.ctrlPressed = False + return + + if not name.scheme(): + name.setUrl(Preferences.getHelp("DefaultScheme") + name.toString()) + + if len(name.scheme()) == 1 or \ + name.scheme() == "file": + # name is a local file + if name.scheme() and len(name.scheme()) == 1: + # it is a local path on win os + name = QUrl.fromLocalFile(name.toString()) + + if not QFileInfo(name.toLocalFile()).exists(): + E5MessageBox.critical( + self, + self.tr("eric6 Web Browser"), + self.tr( + """<p>The file <b>{0}</b> does not exist.</p>""") + .format(name.toLocalFile())) + return + + if name.toLocalFile().endswith(".pdf") or \ + name.toLocalFile().endswith(".PDF") or \ + name.toLocalFile().endswith(".chm") or \ + name.toLocalFile().endswith(".CHM"): + started = QDesktopServices.openUrl(name) + if not started: + E5MessageBox.critical( + self, + self.tr("eric6 Web Browser"), + self.tr( + """<p>Could not start a viewer""" + """ for file <b>{0}</b>.</p>""") + .format(name.path())) + return + elif name.scheme() in ["mailto"]: + started = QDesktopServices.openUrl(name) + if not started: + E5MessageBox.critical( + self, + self.tr("eric6 Web Browser"), + self.tr( + """<p>Could not start an application""" + """ for URL <b>{0}</b>.</p>""") + .format(name.toString())) + return + elif name.scheme() == "javascript": + scriptSource = QUrl.fromPercentEncoding(name.toString( + QUrl.FormattingOptions(QUrl.TolerantMode | QUrl.RemoveScheme))) + self.page().mainFrame().evaluateJavaScript(scriptSource) + return + else: + if name.toString().endswith(".pdf") or \ + name.toString().endswith(".PDF") or \ + name.toString().endswith(".chm") or \ + name.toString().endswith(".CHM"): + started = QDesktopServices.openUrl(name) + if not started: + E5MessageBox.critical( + self, + self.tr("eric6 Web Browser"), + self.tr( + """<p>Could not start a viewer""" + """ for file <b>{0}</b>.</p>""") + .format(name.path())) + return + + if requestData is not None: + self.load(*requestData) + else: + self.load(name) + + def source(self): + """ + Public method to return the URL of the loaded page. + + @return URL loaded in the help browser (QUrl) + """ + return self.url() + + def documentTitle(self): + """ + Public method to return the title of the loaded page. + + @return title (string) + """ + return self.title() + + def backward(self): + """ + Public slot to move backwards in history. + """ + self.triggerPageAction(QWebPage.Back) + self.__urlChanged(self.history().currentItem().url()) + + def forward(self): + """ + Public slot to move forward in history. + """ + self.triggerPageAction(QWebPage.Forward) + self.__urlChanged(self.history().currentItem().url()) + + def home(self): + """ + Public slot to move to the first page loaded. + """ + homeUrl = QUrl(Preferences.getHelp("HomePage")) + self.setSource(homeUrl) + self.__urlChanged(self.history().currentItem().url()) + + def reload(self): + """ + Public slot to reload the current page. + """ + self.triggerPageAction(QWebPage.Reload) + + def copy(self): + """ + Public slot to copy the selected text. + """ + self.triggerPageAction(QWebPage.Copy) + + def isForwardAvailable(self): + """ + Public method to determine, if a forward move in history is possible. + + @return flag indicating move forward is possible (boolean) + """ + return self.history().canGoForward() + + def isBackwardAvailable(self): + """ + Public method to determine, if a backwards move in history is possible. + + @return flag indicating move backwards is possible (boolean) + """ + return self.history().canGoBack() + + def __levelForZoom(self, zoom): + """ + Private method determining the zoom level index given a zoom factor. + + @param zoom zoom factor (integer) + @return index of zoom factor (integer) + """ + try: + index = self.__zoomLevels.index(zoom) + except ValueError: + for index in range(len(self.__zoomLevels)): + if zoom <= self.__zoomLevels[index]: + break + return index + + def __applyZoom(self): + """ + Private slot to apply the current zoom factor. + """ + self.setZoomValue(self.__currentZoom) + + def setZoomValue(self, value, saveValue=True): + """ + Public method to set the zoom value. + + @param value zoom value (integer) + @keyparam saveValue flag indicating to save the zoom value with the + zoom manager + @type bool + """ + if value != self.zoomValue(): + try: + self.setZoomFactor(value / 100.0) + except AttributeError: + self.setTextSizeMultiplier(value / 100.0) + self.__currentZoom = value + if saveValue: + Helpviewer.HelpWindow.HelpWindow.zoomManager().setZoomValue( + self.url(), value) + self.zoomValueChanged.emit(value) + + def zoomValue(self): + """ + Public method to get the current zoom value. + + @return zoom value (integer) + """ + try: + val = self.zoomFactor() * 100 + except AttributeError: + val = self.textSizeMultiplier() * 100 + return int(val) + + def zoomIn(self): + """ + Public slot to zoom into the page. + """ + index = self.__levelForZoom(self.__currentZoom) + if index < len(self.__zoomLevels) - 1: + self.__currentZoom = self.__zoomLevels[index + 1] + self.__applyZoom() + + def zoomOut(self): + """ + Public slot to zoom out of the page. + """ + index = self.__levelForZoom(self.__currentZoom) + if index > 0: + self.__currentZoom = self.__zoomLevels[index - 1] + self.__applyZoom() + + def zoomReset(self): + """ + Public method to reset the zoom factor. + """ + index = self.__levelForZoom(HelpBrowser.ZoomLevelDefault) + self.__currentZoom = self.__zoomLevels[index] + self.__applyZoom() + + def hasSelection(self): + """ + Public method to determine, if there is some text selected. + + @return flag indicating text has been selected (boolean) + """ + return self.selectedText() != "" + + def findNextPrev(self, txt, case, backwards, wrap, highlightAll): + """ + Public slot to find the next occurrence of a text. + + @param txt text to search for (string) + @param case flag indicating a case sensitive search (boolean) + @param backwards flag indicating a backwards search (boolean) + @param wrap flag indicating to wrap around (boolean) + @param highlightAll flag indicating to highlight all occurrences + (boolean) + @return flag indicating that a match was found (boolean) + """ + findFlags = QWebPage.FindFlags() + if case: + findFlags |= QWebPage.FindCaseSensitively + if backwards: + findFlags |= QWebPage.FindBackward + if wrap: + findFlags |= QWebPage.FindWrapsAroundDocument + try: + if highlightAll: + findFlags |= QWebPage.HighlightAllOccurrences + except AttributeError: + pass + + return self.findText(txt, findFlags) + + def __isMediaElement(self, element): + """ + Private method to check, if the given element is a media element. + + @param element element to be checked (QWebElement) + @return flag indicating a media element (boolean) + """ + return element.tagName().lower() in ["video", "audio"] + + def contextMenuEvent(self, evt): + """ + Protected method called to create a context menu. + + This method is overridden from QWebView. + + @param evt reference to the context menu event object + (QContextMenuEvent) + """ + from .UserAgent.UserAgentMenu import UserAgentMenu + menu = QMenu(self) + + frameAtPos = self.page().frameAt(evt.pos()) + hit = self.page().mainFrame().hitTestContent(evt.pos()) + if not hit.linkUrl().isEmpty(): + menu.addAction( + UI.PixmapCache.getIcon("openNewTab.png"), + self.tr("Open Link in New Tab\tCtrl+LMB"), + self.__openLinkInNewTab).setData(hit.linkUrl()) + menu.addSeparator() + menu.addAction( + UI.PixmapCache.getIcon("download.png"), + self.tr("Save Lin&k"), self.__downloadLink) + menu.addAction( + UI.PixmapCache.getIcon("bookmark22.png"), + self.tr("Bookmark this Link"), self.__bookmarkLink)\ + .setData(hit.linkUrl()) + menu.addSeparator() + menu.addAction( + UI.PixmapCache.getIcon("editCopy.png"), + self.tr("Copy Link to Clipboard"), self.__copyLink) + menu.addAction( + UI.PixmapCache.getIcon("mailSend.png"), + self.tr("Send Link"), + self.__sendLink).setData(hit.linkUrl()) + if Preferences.getHelp("VirusTotalEnabled") and \ + Preferences.getHelp("VirusTotalServiceKey") != "": + menu.addAction( + UI.PixmapCache.getIcon("virustotal.png"), + self.tr("Scan Link with VirusTotal"), + self.__virusTotal).setData(hit.linkUrl()) + + if not hit.imageUrl().isEmpty(): + if not menu.isEmpty(): + menu.addSeparator() + menu.addAction( + UI.PixmapCache.getIcon("openNewTab.png"), + self.tr("Open Image in New Tab"), + self.__openLinkInNewTab).setData(hit.imageUrl()) + menu.addSeparator() + menu.addAction( + UI.PixmapCache.getIcon("download.png"), + self.tr("Save Image"), self.__downloadImage) + menu.addAction( + self.tr("Copy Image to Clipboard"), self.__copyImage) + menu.addAction( + UI.PixmapCache.getIcon("editCopy.png"), + self.tr("Copy Image Location to Clipboard"), + self.__copyLocation).setData(hit.imageUrl().toString()) + menu.addAction( + UI.PixmapCache.getIcon("mailSend.png"), + self.tr("Send Image Link"), + self.__sendLink).setData(hit.imageUrl()) + menu.addSeparator() + menu.addAction( + UI.PixmapCache.getIcon("adBlockPlus.png"), + self.tr("Block Image"), self.__blockImage)\ + .setData(hit.imageUrl().toString()) + if Preferences.getHelp("VirusTotalEnabled") and \ + Preferences.getHelp("VirusTotalServiceKey") != "": + menu.addAction( + UI.PixmapCache.getIcon("virustotal.png"), + self.tr("Scan Image with VirusTotal"), + self.__virusTotal).setData(hit.imageUrl()) + + element = hit.element() + if not element.isNull(): + if self.__isMediaElement(element): + if not menu.isEmpty(): + menu.addSeparator() + + self.__clickedMediaElement = element + + paused = element.evaluateJavaScript("this.paused") + muted = element.evaluateJavaScript("this.muted") + videoUrl = QUrl(element.evaluateJavaScript("this.currentSrc")) + + if paused: + menu.addAction( + UI.PixmapCache.getIcon("mediaPlaybackStart.png"), + self.tr("Play"), self.__pauseMedia) + else: + menu.addAction( + UI.PixmapCache.getIcon("mediaPlaybackPause.png"), + self.tr("Pause"), self.__pauseMedia) + if muted: + menu.addAction( + UI.PixmapCache.getIcon("audioVolumeHigh.png"), + self.tr("Unmute"), self.__muteMedia) + else: + menu.addAction( + UI.PixmapCache.getIcon("audioVolumeMuted.png"), + self.tr("Mute"), self.__muteMedia) + menu.addSeparator() + menu.addAction( + UI.PixmapCache.getIcon("editCopy.png"), + self.tr("Copy Media Address to Clipboard"), + self.__copyLocation).setData(videoUrl.toString()) + menu.addAction( + UI.PixmapCache.getIcon("mailSend.png"), + self.tr("Send Media Address"), self.__sendLink)\ + .setData(videoUrl) + menu.addAction( + UI.PixmapCache.getIcon("download.png"), + self.tr("Save Media"), self.__downloadMedia)\ + .setData(videoUrl) + + if element.tagName().lower() in ["input", "textarea"]: + if menu.isEmpty(): + pageMenu = self.page().createStandardContextMenu() + directionFound = False + # used to detect double direction entry + for act in pageMenu.actions(): + if act.isSeparator(): + menu.addSeparator() + continue + if act.menu(): + if self.pageAction( + QWebPage.SetTextDirectionDefault) in \ + act.menu().actions(): + if directionFound: + act.setVisible(False) + directionFound = True + elif self.pageAction(QWebPage.ToggleBold) in \ + act.menu().actions(): + act.setVisible(False) + elif act == self.pageAction(QWebPage.InspectElement): + # we have our own inspect entry + act.setVisible(False) + menu.addAction(act) + pageMenu = None + + if not menu.isEmpty(): + menu.addSeparator() + + self.mw.personalInformationManager().createSubMenu(menu, self, hit) + + menu.addAction(self.mw.newTabAct) + menu.addAction(self.mw.newAct) + menu.addSeparator() + menu.addAction(self.mw.saveAsAct) + menu.addSeparator() + + if frameAtPos and self.page().mainFrame() != frameAtPos: + self.__clickedFrame = frameAtPos + fmenu = QMenu(self.tr("This Frame")) + frameUrl = self.__clickedFrame.url() + if frameUrl.isValid(): + fmenu.addAction( + self.tr("Show &only this frame"), + self.__loadClickedFrame) + fmenu.addAction( + UI.PixmapCache.getIcon("openNewTab.png"), + self.tr("Show in new &tab"), + self.__openLinkInNewTab).setData(self.__clickedFrame.url()) + fmenu.addSeparator() + fmenu.addAction( + UI.PixmapCache.getIcon("print.png"), + self.tr("&Print"), self.__printClickedFrame) + fmenu.addAction( + UI.PixmapCache.getIcon("printPreview.png"), + self.tr("Print Preview"), self.__printPreviewClickedFrame) + fmenu.addAction( + UI.PixmapCache.getIcon("printPdf.png"), + self.tr("Print as PDF"), self.__printPdfClickedFrame) + fmenu.addSeparator() + fmenu.addAction( + UI.PixmapCache.getIcon("zoomIn.png"), + self.tr("Zoom &in"), self.__zoomInClickedFrame) + fmenu.addAction( + UI.PixmapCache.getIcon("zoomReset.png"), + self.tr("Zoom &reset"), self.__zoomResetClickedFrame) + fmenu.addAction( + UI.PixmapCache.getIcon("zoomOut.png"), + self.tr("Zoom &out"), self.__zoomOutClickedFrame) + fmenu.addSeparator() + fmenu.addAction( + self.tr("Show frame so&urce"), + self.__showClickedFrameSource) + + menu.addMenu(fmenu) + menu.addSeparator() + + menu.addAction( + UI.PixmapCache.getIcon("bookmark22.png"), + self.tr("Bookmark this Page"), self.addBookmark) + menu.addAction( + UI.PixmapCache.getIcon("mailSend.png"), + self.tr("Send Page Link"), self.__sendLink).setData(self.url()) + menu.addSeparator() + self.__userAgentMenu = UserAgentMenu(self.tr("User Agent"), + url=self.url()) + menu.addMenu(self.__userAgentMenu) + menu.addSeparator() + menu.addAction(self.mw.backAct) + menu.addAction(self.mw.forwardAct) + menu.addAction(self.mw.homeAct) + menu.addSeparator() + menu.addAction(self.mw.zoomInAct) + menu.addAction(self.mw.zoomResetAct) + menu.addAction(self.mw.zoomOutAct) + menu.addSeparator() + if self.selectedText(): + menu.addAction(self.mw.copyAct) + menu.addAction( + UI.PixmapCache.getIcon("mailSend.png"), + self.tr("Send Text"), + self.__sendLink).setData(self.selectedText()) + menu.addAction(self.mw.findAct) + menu.addSeparator() + if self.selectedText(): + self.__searchMenu = menu.addMenu(self.tr("Search with...")) + + from .OpenSearch.OpenSearchEngineAction import \ + OpenSearchEngineAction + engineNames = self.mw.openSearchManager().allEnginesNames() + for engineName in engineNames: + engine = self.mw.openSearchManager().engine(engineName) + act = OpenSearchEngineAction(engine, self.__searchMenu) + act.setData(engineName) + self.__searchMenu.addAction(act) + self.__searchMenu.triggered.connect(self.__searchRequested) + + menu.addSeparator() + + from .HelpLanguagesDialog import HelpLanguagesDialog + languages = Preferences.toList( + Preferences.Prefs.settings.value( + "Help/AcceptLanguages", + HelpLanguagesDialog.defaultAcceptLanguages())) + if languages: + language = languages[0] + langCode = language.split("[")[1][:2] + googleTranslatorUrl = QUrl( + "http://translate.google.com/#auto|{0}|{1}".format( + langCode, self.selectedText())) + menu.addAction( + UI.PixmapCache.getIcon("translate.png"), + self.tr("Google Translate"), self.__openLinkInNewTab)\ + .setData(googleTranslatorUrl) + wiktionaryUrl = QUrl( + "http://{0}.wiktionary.org/wiki/Special:Search?search={1}" + .format(langCode, self.selectedText())) + menu.addAction( + UI.PixmapCache.getIcon("wikipedia.png"), + self.tr("Dictionary"), self.__openLinkInNewTab)\ + .setData(wiktionaryUrl) + menu.addSeparator() + + guessedUrl = QUrl.fromUserInput(self.selectedText().strip()) + if self.__isUrlValid(guessedUrl): + menu.addAction( + self.tr("Go to web address"), + self.__openLinkInNewTab).setData(guessedUrl) + menu.addSeparator() + + element = hit.element() + if not element.isNull() and \ + element.tagName().lower() == "input" and \ + element.attribute("type", "text") == "text": + menu.addAction(self.tr("Add to web search toolbar"), + self.__addSearchEngine).setData(element) + menu.addSeparator() + + menu.addAction( + UI.PixmapCache.getIcon("webInspector.png"), + self.tr("Web Inspector..."), self.__webInspector) + + menu.exec_(evt.globalPos()) + + def __isUrlValid(self, url): + """ + Private method to check a URL for validity. + + @param url URL to be checked (QUrl) + @return flag indicating a valid URL (boolean) + """ + return url.isValid() and \ + bool(url.host()) and \ + bool(url.scheme()) and \ + "." in url.host() + + def __openLinkInNewTab(self): + """ + Private method called by the context menu to open a link in a new + window. + """ + act = self.sender() + url = act.data() + if url.isEmpty(): + return + + self.ctrlPressed = True + self.setSource(url) + self.ctrlPressed = False + + def __bookmarkLink(self): + """ + Private slot to bookmark a link via the context menu. + """ + act = self.sender() + url = act.data() + if url.isEmpty(): + return + + from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog + dlg = AddBookmarkDialog() + dlg.setUrl(bytes(url.toEncoded()).decode()) + dlg.exec_() + + def __sendLink(self): + """ + Private slot to send a link via email. + """ + act = self.sender() + data = act.data() + if isinstance(data, QUrl) and data.isEmpty(): + return + + if isinstance(data, QUrl): + data = data.toString() + QDesktopServices.openUrl(QUrl("mailto:?body=" + data)) + + def __downloadLink(self): + """ + Private slot to download a link and save it to disk. + """ + self.pageAction(QWebPage.DownloadLinkToDisk).trigger() + + def __copyLink(self): + """ + Private slot to copy a link to the clipboard. + """ + self.pageAction(QWebPage.CopyLinkToClipboard).trigger() + + def __downloadImage(self): + """ + Private slot to download an image and save it to disk. + """ + self.pageAction(QWebPage.DownloadImageToDisk).trigger() + + def __copyImage(self): + """ + Private slot to copy an image to the clipboard. + """ + self.pageAction(QWebPage.CopyImageToClipboard).trigger() + + def __copyLocation(self): + """ + Private slot to copy an image or media location to the clipboard. + """ + act = self.sender() + url = act.data() + QApplication.clipboard().setText(url) + + def __blockImage(self): + """ + Private slot to add a block rule for an image URL. + """ + import Helpviewer.HelpWindow + act = self.sender() + url = act.data() + dlg = Helpviewer.HelpWindow.HelpWindow.adBlockManager().showDialog() + dlg.addCustomRule(url) + + def __downloadMedia(self): + """ + Private slot to download a media and save it to disk. + """ + act = self.sender() + url = act.data() + self.mw.downloadManager().download(url, True, mainWindow=self.mw) + + def __pauseMedia(self): + """ + Private slot to pause or play the selected media. + """ + paused = self.__clickedMediaElement.evaluateJavaScript("this.paused") + + if paused: + self.__clickedMediaElement.evaluateJavaScript("this.play()") + else: + self.__clickedMediaElement.evaluateJavaScript("this.pause()") + + def __muteMedia(self): + """ + Private slot to (un)mute the selected media. + """ + muted = self.__clickedMediaElement.evaluateJavaScript("this.muted") + + if muted: + self.__clickedMediaElement.evaluateJavaScript("this.muted = false") + else: + self.__clickedMediaElement.evaluateJavaScript("this.muted = true") + + def __virusTotal(self): + """ + Private slot to scan the selected URL with VirusTotal. + """ + act = self.sender() + url = act.data() + self.mw.requestVirusTotalScan(url) + + def __searchRequested(self, act): + """ + Private slot to search for some text with a selected search engine. + + @param act reference to the action that triggered this slot (QAction) + """ + searchText = self.selectedText() + + if not searchText: + return + + engineName = act.data() + if engineName: + engine = self.mw.openSearchManager().engine(engineName) + self.search.emit(engine.searchUrl(searchText)) + + def __addSearchEngine(self): + """ + Private slot to add a new search engine. + """ + act = self.sender() + if act is None: + return + + element = act.data() + elementName = element.attribute("name") + formElement = QWebElement(element) + while formElement.tagName().lower() != "form": + formElement = formElement.parent() + + if formElement.isNull() or \ + formElement.attribute("action") == "": + return + + method = formElement.attribute("method", "get").lower() + if method != "get": + E5MessageBox.warning( + self, + self.tr("Method not supported"), + self.tr( + """{0} method is not supported.""").format(method.upper())) + return + + searchUrl = QUrl(self.page().mainFrame().baseUrl().resolved( + QUrl(formElement.attribute("action")))) + if searchUrl.scheme() != "http": + return + + if qVersion() >= "5.0.0": + from PyQt5.QtCore import QUrlQuery + searchUrlQuery = QUrlQuery(searchUrl) + searchEngines = {} + inputFields = formElement.findAll("input") + for inputField in inputFields.toList(): + type_ = inputField.attribute("type", "text") + name = inputField.attribute("name") + value = inputField.evaluateJavaScript("this.value") + + if type_ == "submit": + searchEngines[value] = name + elif type_ == "text": + if inputField == element: + value = "{searchTerms}" + if qVersion() >= "5.0.0": + searchUrlQuery.addQueryItem(name, value) + else: + searchUrl.addQueryItem(name, value) + elif type_ == "checkbox" or type_ == "radio": + if inputField.evaluateJavaScript("this.checked"): + if qVersion() >= "5.0.0": + searchUrlQuery.addQueryItem(name, value) + else: + searchUrl.addQueryItem(name, value) + elif type_ == "hidden": + if qVersion() >= "5.0.0": + searchUrlQuery.addQueryItem(name, value) + else: + searchUrl.addQueryItem(name, value) + + selectFields = formElement.findAll("select") + for selectField in selectFields.toList(): + name = selectField.attribute("name") + selectedIndex = selectField.evaluateJavaScript( + "this.selectedIndex") + if selectedIndex == -1: + continue + + options = selectField.findAll("option") + value = options.at(selectedIndex).toPlainText() + if qVersion() >= "5.0.0": + searchUrlQuery.addQueryItem(name, value) + else: + searchUrl.addQueryItem(name, value) + + ok = True + if len(searchEngines) > 1: + searchEngine, ok = QInputDialog.getItem( + self, + self.tr("Search engine"), + self.tr("Choose the desired search engine"), + sorted(searchEngines.keys()), 0, False) + + if not ok: + return + + if searchEngines[searchEngine] != "": + if qVersion() >= "5.0.0": + searchUrlQuery.addQueryItem( + searchEngines[searchEngine], searchEngine) + else: + searchUrl.addQueryItem( + searchEngines[searchEngine], searchEngine) + engineName = "" + labels = formElement.findAll('label[for="{0}"]'.format(elementName)) + if labels.count() > 0: + engineName = labels.at(0).toPlainText() + + engineName, ok = QInputDialog.getText( + self, + self.tr("Engine name"), + self.tr("Enter a name for the engine"), + QLineEdit.Normal, + engineName) + if not ok: + return + + if qVersion() >= "5.0.0": + searchUrl.setQuery(searchUrlQuery) + + from .OpenSearch.OpenSearchEngine import OpenSearchEngine + engine = OpenSearchEngine() + engine.setName(engineName) + engine.setDescription(engineName) + engine.setSearchUrlTemplate(searchUrl.toString()) + engine.setImage(self.icon().pixmap(16, 16).toImage()) + + self.mw.openSearchManager().addEngine(engine) + + def __webInspector(self): + """ + Private slot to show the web inspector window. + """ + if self.__inspector is None: + from .HelpInspector import HelpInspector + self.__inspector = HelpInspector() + self.__inspector.setPage(self.page()) + self.__inspector.show() + elif self.__inspector.isVisible(): + self.__inspector.hide() + else: + self.__inspector.show() + + def closeWebInspector(self): + """ + Public slot to close the web inspector. + """ + if self.__inspector is not None: + if self.__inspector.isVisible(): + self.__inspector.hide() + self.__inspector.deleteLater() + self.__inspector = None + + def addBookmark(self): + """ + Public slot to bookmark the current page. + """ + from .Bookmarks.AddBookmarkDialog import AddBookmarkDialog + dlg = AddBookmarkDialog() + dlg.setUrl(bytes(self.url().toEncoded()).decode()) + dlg.setTitle(self.title()) + meta = self.page().mainFrame().metaData() + if "description" in meta: + dlg.setDescription(meta["description"][0]) + dlg.exec_() + + def dragEnterEvent(self, evt): + """ + Protected method called by a drag enter event. + + @param evt reference to the drag enter event (QDragEnterEvent) + """ + evt.acceptProposedAction() + + def dragMoveEvent(self, evt): + """ + Protected method called by a drag move event. + + @param evt reference to the drag move event (QDragMoveEvent) + """ + evt.ignore() + if evt.source() != self: + if len(evt.mimeData().urls()) > 0: + evt.acceptProposedAction() + else: + url = QUrl(evt.mimeData().text()) + if url.isValid(): + evt.acceptProposedAction() + + if not evt.isAccepted(): + super(HelpBrowser, self).dragMoveEvent(evt) + + def dropEvent(self, evt): + """ + Protected method called by a drop event. + + @param evt reference to the drop event (QDropEvent) + """ + super(HelpBrowser, self).dropEvent(evt) + if not evt.isAccepted() and \ + evt.source() != self and \ + evt.possibleActions() & Qt.CopyAction: + url = QUrl() + if len(evt.mimeData().urls()) > 0: + url = evt.mimeData().urls()[0] + if not url.isValid(): + url = QUrl(evt.mimeData().text()) + if url.isValid(): + self.setSource(url) + evt.acceptProposedAction() + + def mousePressEvent(self, evt): + """ + Protected method called by a mouse press event. + + @param evt reference to the mouse event (QMouseEvent) + """ + self.mw.setEventMouseButtons(evt.buttons()) + self.mw.setEventKeyboardModifiers(evt.modifiers()) + + if evt.button() == Qt.XButton1: + self.pageAction(QWebPage.Back).trigger() + elif evt.button() == Qt.XButton2: + self.pageAction(QWebPage.Forward).trigger() + else: + super(HelpBrowser, self).mousePressEvent(evt) + + def mouseReleaseEvent(self, evt): + """ + Protected method called by a mouse release event. + + @param evt reference to the mouse event (QMouseEvent) + """ + accepted = evt.isAccepted() + self.__page.event(evt) + if not evt.isAccepted() and \ + self.mw.eventMouseButtons() & Qt.MidButton: + url = QUrl(QApplication.clipboard().text(QClipboard.Selection)) + if not url.isEmpty() and \ + url.isValid() and \ + url.scheme() != "": + self.mw.setEventMouseButtons(Qt.NoButton) + self.mw.setEventKeyboardModifiers(Qt.NoModifier) + self.setSource(url) + evt.setAccepted(accepted) + + def wheelEvent(self, evt): + """ + Protected method to handle wheel events. + + @param evt reference to the wheel event (QWheelEvent) + """ + if qVersion() >= "5.0.0": + delta = evt.angleDelta().y() + else: + delta = evt.delta() + if evt.modifiers() & Qt.ControlModifier: + if delta < 0: + self.zoomOut() + else: + self.zoomIn() + evt.accept() + return + + if evt.modifiers() & Qt.ShiftModifier: + if delta < 0: + self.backward() + else: + self.forward() + evt.accept() + return + + super(HelpBrowser, self).wheelEvent(evt) + + def keyPressEvent(self, evt): + """ + Protected method called by a key press. + + @param evt reference to the key event (QKeyEvent) + """ + if self.mw.personalInformationManager().viewKeyPressEvent(self, evt): + return + + if self.__enableAccessKeys: + self.__accessKeysPressed = ( + evt.modifiers() == Qt.ControlModifier and + evt.key() == Qt.Key_Control) + if not self.__accessKeysPressed: + if self.__checkForAccessKey(evt): + self.__hideAccessKeys() + evt.accept() + return + self.__hideAccessKeys() + else: + QTimer.singleShot(300, self.__accessKeyShortcut) + + self.ctrlPressed = (evt.key() == Qt.Key_Control) + super(HelpBrowser, self).keyPressEvent(evt) + + def keyReleaseEvent(self, evt): + """ + Protected method called by a key release. + + @param evt reference to the key event (QKeyEvent) + """ + if self.__enableAccessKeys: + self.__accessKeysPressed = evt.key() == Qt.Key_Control + + self.ctrlPressed = False + super(HelpBrowser, self).keyReleaseEvent(evt) + + def focusOutEvent(self, evt): + """ + Protected method called by a focus out event. + + @param evt reference to the focus event (QFocusEvent) + """ + if self.__accessKeysPressed: + self.__hideAccessKeys() + self.__accessKeysPressed = False + + super(HelpBrowser, self).focusOutEvent(evt) + + def event(self, evt): + """ + Public method handling events. + + @param evt reference to the event (QEvent) + @return flag indicating, if the event was handled (boolean) + """ + if evt.type() == QEvent.Gesture: + self.gestureEvent(evt) + return True + + return super(HelpBrowser, self).event(evt) + + def gestureEvent(self, evt): + """ + Protected method handling gesture events. + + @param evt reference to the gesture event (QGestureEvent + """ + pinch = evt.gesture(Qt.PinchGesture) + if pinch: + if pinch.state() == Qt.GestureStarted: + pinch.setScaleFactor(self.__currentZoom / 100.0) + else: + scaleFactor = pinch.scaleFactor() + self.__currentZoom = int(scaleFactor * 100) + self.__applyZoom() + evt.accept() + + def clearHistory(self): + """ + Public slot to clear the history. + """ + self.history().clear() + self.__urlChanged(self.history().currentItem().url()) + + ########################################################################### + ## Signal converters below + ########################################################################### + + def __urlChanged(self, url): + """ + Private slot to handle the urlChanged signal. + + @param url the new url (QUrl) + """ + self.sourceChanged.emit(url) + + self.forwardAvailable.emit(self.isForwardAvailable()) + self.backwardAvailable.emit(self.isBackwardAvailable()) + + def __statusBarMessage(self, text): + """ + Private slot to handle the statusBarMessage signal. + + @param text text to be shown in the status bar (string) + """ + self.mw.statusBar().showMessage(text) + + def __linkHovered(self, link, title, textContent): + """ + Private slot to handle the linkHovered signal. + + @param link the URL of the link (string) + @param title the link title (string) + @param textContent text content of the link (string) + """ + self.highlighted.emit(link) + + ########################################################################### + ## Signal handlers below + ########################################################################### + + def __loadStarted(self): + """ + Private method to handle the loadStarted signal. + """ + self.__isLoading = True + self.__progress = 0 + + def __loadProgress(self, progress): + """ + Private method to handle the loadProgress signal. + + @param progress progress value (integer) + """ + self.__progress = progress + + def __loadFinished(self, ok): + """ + Private method to handle the loadFinished signal. + + @param ok flag indicating the result (boolean) + """ + self.__isLoading = False + self.__progress = 0 + + if Preferences.getHelp("ClickToFlashEnabled"): + # this is a hack to make the ClickToFlash button appear + self.zoomIn() + self.zoomOut() + + zoomValue = Helpviewer.HelpWindow.HelpWindow.zoomManager()\ + .zoomValue(self.url()) + self.setZoomValue(zoomValue) + + if ok: + self.mw.adBlockManager().page().hideBlockedPageEntries(self.page()) + self.mw.passwordManager().fill(self.page()) + + def isLoading(self): + """ + Public method to get the loading state. + + @return flag indicating the loading state (boolean) + """ + return self.__isLoading + + def progress(self): + """ + Public method to get the load progress. + + @return load progress (integer) + """ + return self.__progress + + def saveAs(self): + """ + Public method to save the current page to a file. + """ + url = self.url() + if url.isEmpty(): + return + + self.mw.downloadManager().download(url, True, mainWindow=self.mw) + + def __unsupportedContent(self, reply, requestFilename=None, + download=False): + """ + Private slot to handle the unsupportedContent signal. + + @param reply reference to the reply object (QNetworkReply) + @keyparam requestFilename indicating to ask for a filename + (boolean or None). If it is None, the behavior is determined + by a configuration option. + @keyparam download flag indicating a download operation (boolean) + """ + if reply is None: + return + + replyUrl = reply.url() + + if replyUrl.scheme() == "abp": + return + + if reply.error() == QNetworkReply.NoError: + if reply.header(QNetworkRequest.ContentTypeHeader): + self.mw.downloadManager().handleUnsupportedContent( + reply, webPage=self.page(), mainWindow=self.mw) + return + + replyUrl = reply.url() + if replyUrl.isEmpty(): + return + + notFoundFrame = self.page().mainFrame() + if notFoundFrame is None: + return + + if reply.header(QNetworkRequest.ContentTypeHeader): + data = reply.readAll() + if contentSniff(data): + notFoundFrame.setHtml(str(data, encoding="utf-8"), replyUrl) + return + + urlString = bytes(replyUrl.toEncoded()).decode() + title = self.tr("Error loading page: {0}").format(urlString) + htmlFile = QFile(":/html/notFoundPage.html") + htmlFile.open(QFile.ReadOnly) + html = htmlFile.readAll() + pixmap = qApp.style()\ + .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(48, 48) + imageBuffer = QBuffer() + imageBuffer.open(QIODevice.ReadWrite) + if pixmap.save(imageBuffer, "PNG"): + html = html.replace("@IMAGE@", imageBuffer.buffer().toBase64()) + pixmap = qApp.style()\ + .standardIcon(QStyle.SP_MessageBoxWarning).pixmap(16, 16) + imageBuffer = QBuffer() + imageBuffer.open(QIODevice.ReadWrite) + if pixmap.save(imageBuffer, "PNG"): + html = html.replace("@FAVICON@", imageBuffer.buffer().toBase64()) + html = html.replace("@TITLE@", title.encode("utf8")) + html = html.replace("@H1@", reply.errorString().encode("utf8")) + html = html.replace( + "@H2@", self.tr("When connecting to: {0}.") + .format(urlString).encode("utf8")) + html = html.replace( + "@LI-1@", + self.tr("Check the address for errors such as " + "<b>ww</b>.example.org instead of " + "<b>www</b>.example.org").encode("utf8")) + html = html.replace( + "@LI-2@", + self.tr("If the address is correct, try checking the network " + "connection.").encode("utf8")) + html = html.replace( + "@LI-3@", + self.tr( + "If your computer or network is protected by a firewall " + "or proxy, make sure that the browser is permitted to " + "access the network.").encode("utf8")) + html = html.replace( + "@LI-4@", + self.tr("If your cache policy is set to offline browsing," + "only pages in the local cache are available.") + .encode("utf8")) + html = html.replace( + "@BUTTON@", self.tr("Try Again").encode("utf8")) + notFoundFrame.setHtml(bytes(html).decode("utf8"), replyUrl) + self.mw.historyManager().removeHistoryEntry(replyUrl, self.title()) + self.loadFinished.emit(False) + + def __featurePermissionRequested(self, frame, feature): + """ + Private slot handling a feature permission request. + + @param frame frame sending the request + @type QWebFrame + @param feature requested feature + @type QWebPage.Feature + """ + manager = Helpviewer.HelpWindow.HelpWindow.featurePermissionManager() + manager.requestFeaturePermission(self.page(), frame, feature) + + def __downloadRequested(self, request): + """ + Private slot to handle a download request. + + @param request reference to the request object (QNetworkRequest) + """ + self.mw.downloadManager().download(request, mainWindow=self.mw) + + def __databaseQuotaExceeded(self, frame, databaseName): + """ + Private slot to handle the case, where the database quota is exceeded. + + @param frame reference to the frame (QWebFrame) + @param databaseName name of the web database (string) + """ + securityOrigin = frame.securityOrigin() + if securityOrigin.databaseQuota() > 0 and \ + securityOrigin.databaseUsage() == 0: + # cope with a strange behavior of Qt 4.6, if a database is + # accessed for the first time + return + + res = E5MessageBox.yesNo( + self, + self.tr("Web Database Quota"), + self.tr( + """<p>The database quota of <strong>{0}</strong> has""" + """ been exceeded while accessing database <strong>{1}""" + """</strong>.</p><p>Shall it be changed?</p>""") + .format(self.__dataString(securityOrigin.databaseQuota()), + databaseName), + yesDefault=True) + if res: + newQuota, ok = QInputDialog.getInt( + self, + self.tr("New Web Database Quota"), + self.tr( + "Enter the new quota in MB (current = {0}, used = {1}; " + "step size = 5 MB):" + .format( + self.__dataString(securityOrigin.databaseQuota()), + self.__dataString(securityOrigin.databaseUsage()))), + securityOrigin.databaseQuota() // (1024 * 1024), + 0, 2147483647, 5) + if ok: + securityOrigin.setDatabaseQuota(newQuota * 1024 * 1024) + + def __dataString(self, size): + """ + Private method to generate a formatted data string. + + @param size size to be formatted (integer) + @return formatted data string (string) + """ + unit = "" + if size < 1024: + unit = self.tr("bytes") + elif size < 1024 * 1024: + size /= 1024 + unit = self.tr("kB") + else: + size /= 1024 * 1024 + unit = self.tr("MB") + return "{0:.1f} {1}".format(size, unit) + + ########################################################################### + ## Access key related methods below + ########################################################################### + + def __accessKeyShortcut(self): + """ + Private slot to switch the display of access keys. + """ + if not self.hasFocus() or \ + not self.__accessKeysPressed or \ + not self.__enableAccessKeys: + return + + if self.__accessKeyLabels: + self.__hideAccessKeys() + else: + self.__showAccessKeys() + + self.__accessKeysPressed = False + + def __checkForAccessKey(self, evt): + """ + Private method to check the existence of an access key and activate the + corresponding link. + + @param evt reference to the key event (QKeyEvent) + @return flag indicating, if the event was handled (boolean) + """ + if not self.__accessKeyLabels: + return False + + text = evt.text() + if not text: + return False + + key = text[0].upper() + handled = False + if key in self.__accessKeyNodes: + element = self.__accessKeyNodes[key] + p = element.geometry().center() + frame = element.webFrame() + p -= frame.scrollPosition() + frame = frame.parentFrame() + while frame and frame != self.page().mainFrame(): + p -= frame.scrollPosition() + frame = frame.parentFrame() + pevent = QMouseEvent( + QEvent.MouseButtonPress, p, Qt.LeftButton, + Qt.MouseButtons(Qt.NoButton), + Qt.KeyboardModifiers(Qt.NoModifier)) + qApp.sendEvent(self, pevent) + revent = QMouseEvent( + QEvent.MouseButtonRelease, p, Qt.LeftButton, + Qt.MouseButtons(Qt.NoButton), + Qt.KeyboardModifiers(Qt.NoModifier)) + qApp.sendEvent(self, revent) + handled = True + + return handled + + def __hideAccessKeys(self): + """ + Private slot to hide the access key labels. + """ + if self.__accessKeyLabels: + for label in self.__accessKeyLabels: + label.hide() + label.deleteLater() + self.__accessKeyLabels = [] + self.__accessKeyNodes = {} + self.update() + + def __showAccessKeys(self): + """ + Private method to show the access key labels. + """ + supportedElements = [ + "input", "a", "area", "button", "label", "legend", "textarea", + ] + unusedKeys = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z" \ + " 0 1 2 3 4 5 6 7 8 9".split() + + viewport = QRect(self.__page.mainFrame().scrollPosition(), + self.__page.viewportSize()) + # Priority first goes to elements with accesskey attributes + alreadyLabeled = [] + for elementType in supportedElements: + result = self.page().mainFrame().findAllElements(elementType)\ + .toList() + for element in result: + geometry = element.geometry() + if geometry.size().isEmpty() or \ + not viewport.contains(geometry.topLeft()): + continue + + accessKeyAttribute = element.attribute("accesskey").upper() + if not accessKeyAttribute: + continue + + accessKey = "" + i = 0 + while i < len(accessKeyAttribute): + if accessKeyAttribute[i] in unusedKeys: + accessKey = accessKeyAttribute[i] + break + i += 2 + if accessKey == "": + continue + unusedKeys.remove(accessKey) + self.__makeAccessLabel(accessKey, element) + alreadyLabeled.append(element) + + # Pick an access key first from the letters in the text and then + # from the list of unused access keys + for elementType in supportedElements: + result = self.page().mainFrame().findAllElements(elementType)\ + .toList() + for element in result: + geometry = element.geometry() + if not unusedKeys or \ + element in alreadyLabeled or \ + geometry.size().isEmpty() or \ + not viewport.contains(geometry.topLeft()): + continue + + accessKey = "" + text = element.toPlainText().upper() + for c in text: + if c in unusedKeys: + accessKey = c + break + if accessKey == "": + accessKey = unusedKeys[0] + unusedKeys.remove(accessKey) + self.__makeAccessLabel(accessKey, element) + + def __makeAccessLabel(self, accessKey, element): + """ + Private method to generate the access label for an element. + + @param accessKey access key to generate the label for (str) + @param element reference to the web element to create the label for + (QWebElement) + """ + label = QLabel(self) + label.setText("<qt><b>{0}</b></qt>".format(accessKey)) + + p = QToolTip.palette() + color = QColor(Qt.yellow).lighter(150) + color.setAlpha(175) + p.setColor(QPalette.Window, color) + label.setPalette(p) + label.setAutoFillBackground(True) + label.setFrameStyle(QFrame.Box | QFrame.Plain) + point = element.geometry().center() + point -= self.__page.mainFrame().scrollPosition() + label.move(point) + label.show() + point.setX(point.x() - label.width() // 2) + label.move(point) + self.__accessKeyLabels.append(label) + self.__accessKeyNodes[accessKey] = element + + ########################################################################### + ## Miscellaneous methods below + ########################################################################### + + def createWindow(self, windowType): + """ + Public method called, when a new window should be created. + + @param windowType type of the requested window (QWebPage.WebWindowType) + @return reference to the created browser window (HelpBrowser) + """ + self.mw.newTab(addNextTo=self) + return self.mw.currentBrowser() + + def preferencesChanged(self): + """ + Public method to indicate a change of the settings. + """ + self.__enableAccessKeys = Preferences.getHelp("AccessKeysEnabled") + if not self.__enableAccessKeys: + self.__hideAccessKeys() + + self.reload() + + ########################################################################### + ## RSS related methods below + ########################################################################### + + def checkRSS(self): + """ + Public method to check, if the loaded page contains feed links. + + @return flag indicating the existence of feed links (boolean) + """ + self.__rss = [] + + frame = self.page().mainFrame() + linkElementsList = frame.findAllElements("link").toList() + + for linkElement in linkElementsList: + # only atom+xml and rss+xml will be processed + if linkElement.attribute("rel") != "alternate" or \ + (linkElement.attribute("type") != "application/rss+xml" and + linkElement.attribute("type") != "application/atom+xml"): + continue + + title = linkElement.attribute("title") + href = linkElement.attribute("href") + if href == "" or title == "": + continue + self.__rss.append((title, href)) + + return len(self.__rss) > 0 + + def getRSS(self): + """ + Public method to get the extracted RSS feeds. + + @return list of RSS feeds (list of tuples of two strings) + """ + return self.__rss + + def hasRSS(self): + """ + Public method to check, if the loaded page has RSS links. + + @return flag indicating the presence of RSS links (boolean) + """ + return len(self.__rss) > 0 + + ########################################################################### + ## Clicked Frame slots + ########################################################################### + + def __loadClickedFrame(self): + """ + Private slot to load the selected frame only. + """ + self.setSource(self.__clickedFrame.url()) + + def __printClickedFrame(self): + """ + Private slot to print the selected frame. + """ + printer = QPrinter(mode=QPrinter.HighResolution) + if Preferences.getPrinter("ColorMode"): + printer.setColorMode(QPrinter.Color) + else: + printer.setColorMode(QPrinter.GrayScale) + if Preferences.getPrinter("FirstPageFirst"): + printer.setPageOrder(QPrinter.FirstPageFirst) + else: + printer.setPageOrder(QPrinter.LastPageFirst) + printer.setPageMargins( + Preferences.getPrinter("LeftMargin") * 10, + Preferences.getPrinter("TopMargin") * 10, + Preferences.getPrinter("RightMargin") * 10, + Preferences.getPrinter("BottomMargin") * 10, + QPrinter.Millimeter + ) + printerName = Preferences.getPrinter("PrinterName") + if printerName: + printer.setPrinterName(printerName) + + printDialog = QPrintDialog(printer, self) + if printDialog.exec_() == QDialog.Accepted: + try: + self.__clickedFrame.print_(printer) + except AttributeError: + E5MessageBox.critical( + self, + self.tr("eric6 Web Browser"), + self.tr( + """<p>Printing is not available due to a bug in""" + """ PyQt5. Please upgrade.</p>""")) + + def __printPreviewClickedFrame(self): + """ + Private slot to show a print preview of the clicked frame. + """ + from PyQt5.QtPrintSupport import QPrintPreviewDialog + + printer = QPrinter(mode=QPrinter.HighResolution) + if Preferences.getPrinter("ColorMode"): + printer.setColorMode(QPrinter.Color) + else: + printer.setColorMode(QPrinter.GrayScale) + if Preferences.getPrinter("FirstPageFirst"): + printer.setPageOrder(QPrinter.FirstPageFirst) + else: + printer.setPageOrder(QPrinter.LastPageFirst) + printer.setPageMargins( + Preferences.getPrinter("LeftMargin") * 10, + Preferences.getPrinter("TopMargin") * 10, + Preferences.getPrinter("RightMargin") * 10, + Preferences.getPrinter("BottomMargin") * 10, + QPrinter.Millimeter + ) + printerName = Preferences.getPrinter("PrinterName") + if printerName: + printer.setPrinterName(printerName) + + preview = QPrintPreviewDialog(printer, self) + preview.paintRequested.connect(self.__generatePrintPreviewClickedFrame) + preview.exec_() + + def __generatePrintPreviewClickedFrame(self, printer): + """ + Private slot to generate a print preview of the clicked frame. + + @param printer reference to the printer object (QPrinter) + """ + try: + self.__clickedFrame.print_(printer) + except AttributeError: + E5MessageBox.critical( + self, + self.tr("eric6 Web Browser"), + self.tr( + """<p>Printing is not available due to a bug in PyQt5.""" + """Please upgrade.</p>""")) + return + + def __printPdfClickedFrame(self): + """ + Private slot to print the selected frame to PDF. + """ + printer = QPrinter(mode=QPrinter.HighResolution) + if Preferences.getPrinter("ColorMode"): + printer.setColorMode(QPrinter.Color) + else: + printer.setColorMode(QPrinter.GrayScale) + printerName = Preferences.getPrinter("PrinterName") + if printerName: + printer.setPrinterName(printerName) + printer.setOutputFormat(QPrinter.PdfFormat) + name = self.__clickedFrame.url().path().rsplit('/', 1)[-1] + if name: + name = name.rsplit('.', 1)[0] + name += '.pdf' + printer.setOutputFileName(name) + + printDialog = QPrintDialog(printer, self) + if printDialog.exec_() == QDialog.Accepted: + try: + self.__clickedFrame.print_(printer) + except AttributeError: + E5MessageBox.critical( + self, + self.tr("eric6 Web Browser"), + self.tr( + """<p>Printing is not available due to a bug in""" + """ PyQt5. Please upgrade.</p>""")) + return + + def __zoomInClickedFrame(self): + """ + Private slot to zoom into the clicked frame. + """ + index = self.__levelForZoom( + int(self.__clickedFrame.zoomFactor() * 100)) + if index < len(self.__zoomLevels) - 1: + self.__clickedFrame.setZoomFactor( + self.__zoomLevels[index + 1] / 100) + + def __zoomResetClickedFrame(self): + """ + Private slot to reset the zoom factor of the clicked frame. + """ + self.__clickedFrame.setZoomFactor(self.__currentZoom / 100) + + def __zoomOutClickedFrame(self): + """ + Private slot to zoom out of the clicked frame. + """ + index = self.__levelForZoom( + int(self.__clickedFrame.zoomFactor() * 100)) + if index > 0: + self.__clickedFrame.setZoomFactor( + self.__zoomLevels[index - 1] / 100) + + def __showClickedFrameSource(self): + """ + Private slot to show the source of the clicked frame. + """ + from QScintilla.MiniEditor import MiniEditor + src = self.__clickedFrame.toHtml() + editor = MiniEditor(parent=self) + editor.setText(src, "Html") + editor.setLanguage("dummy.html") + editor.show() + + +def contentSniff(data): + """ + Module function to do some content sniffing to check, if the data is HTML. + + @param data data block to sniff at (string) + @return flag indicating HTML content (boolean) + """ + if data.contains("<!doctype") or \ + data.contains("<script") or \ + data.contains("<html") or \ + data.contains("<!--") or \ + data.contains("<head") or \ + data.contains("<iframe") or \ + data.contains("<h1") or \ + data.contains("<div") or \ + data.contains("<font") or \ + data.contains("<table") or \ + data.contains("<a") or \ + data.contains("<style") or \ + data.contains("<title") or \ + data.contains("<b") or \ + data.contains("<body") or \ + data.contains("<br") or \ + data.contains("<p"): + return True + + return False
--- a/eric6.e4p Sun Jan 31 18:48:43 2016 +0100 +++ b/eric6.e4p Mon Feb 01 20:03:22 2016 +0100 @@ -1264,10 +1264,13 @@ <Source>ViewManager/BookmarkedFilesDialog.py</Source> <Source>ViewManager/ViewManager.py</Source> <Source>ViewManager/__init__.py</Source> + <Source>WebBrowser/SearchWidget.py</Source> <Source>WebBrowser/UrlBar/StackedUrlBar.py</Source> <Source>WebBrowser/UrlBar/UrlBar.py</Source> <Source>WebBrowser/UrlBar/__init__.py</Source> + <Source>WebBrowser/WebBrowserPage.py</Source> <Source>WebBrowser/WebBrowserTabWidget.py</Source> + <Source>WebBrowser/WebBrowserView.py</Source> <Source>WebBrowser/WebBrowserWindow.py</Source> <Source>WebBrowser/__init__.py</Source> <Source>__init__.py</Source> @@ -1667,6 +1670,7 @@ <Form>VCS/CommandOptionsDialog.ui</Form> <Form>VCS/RepositoryInfoDialog.ui</Form> <Form>ViewManager/BookmarkedFilesDialog.ui</Form> + <Form>WebBrowser/SearchWidget.ui</Form> </Forms> <Translations> <Translation>i18n/eric6_cs.qm</Translation>