Mon, 08 Feb 2016 20:56:16 +0100
Continued porting the web browser.
- added web hit test functionality
--- a/WebBrowser/Tools/Scripts.py Sun Feb 07 19:36:07 2016 +0100 +++ b/WebBrowser/Tools/Scripts.py Mon Feb 08 20:56:16 2016 +0100 @@ -7,6 +7,11 @@ Module containing function to generate JavaScript code. """ +# +# This code was ported from QupZilla. +# Copyright (C) David Rosca <nowrep@gmail.com> +# + from __future__ import unicode_literals from .WebBrowserTools import readAllFileContents
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Tools/WebHitTestResult.py Mon Feb 08 20:56:16 2016 +0100 @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an object for testing certain aspects of a web page. +""" + +# +# This code was ported from QupZilla. +# Copyright (C) David Rosca <nowrep@gmail.com> +# + +from __future__ import unicode_literals + +from PyQt5.QtCore import QPoint, QRect, QUrl + +class WebHitTestResult(object): + """ + Class implementing an object for testing certain aspects of a web page. + """ + def __init__(self, page, pos): + """ + Constructor + + @param page reference to the web page + @type WebBrowserPage + @param pos position to be tested + @type QPoint + """ + self.__isNull = True + self.__isContentEditable = False + self.__isContentSelected = False + self.__isMediaPaused = False + self.__isMediaMuted = False + self.__pos = QPoint(pos) + self.__alternateText = "" + self.__boundingRect = QRect() + self.__imageUrl = QUrl() + self.__linkTitle = "" + self.__linkUrl = QUrl() + self.__mediaUrl = QUrl() + self.__tagName = "" + + script = """ + (function() {{ + var e = document.elementFromPoint({0}, {1}); + if (!e) + return; + function isMediaElement(e) {{ + return e.tagName == 'AUDIO' || e.tagName == 'VIDEO'; + }} + function isEditableElement(e) {{ + if (e.isContentEditable) + return true; + if (e.tagName == 'INPUT' || e.tagName == 'TEXTAREA') + return e.getAttribute('readonly') != 'readonly'; + return false; + }} + function isSelected(e) {{ + var selection = window.getSelection(); + if (selection.type != 'Range') + return false; + return window.getSelection().containsNode(e, true); + }} + var res = {{ + alternateText: e.getAttribute('alt'), + boundingRect: '', + imageUrl: '', + contentEditable: isEditableElement(e), + contentSelected: isSelected(e), + linkTitle: '', + linkUrl: '', + mediaUrl: '', + mediaPaused: false, + mediaMuted: false, + tagName: e.tagName.toLowerCase() + }}; + var r = e.getBoundingClientRect(); + res.boundingRect = [r.top, r.left, r.width, r.height]; + if (e.tagName == 'IMG') + res.imageUrl = e.getAttribute('src'); + if (e.tagName == 'A') {{ + res.linkTitle = e.text; + res.linkUrl = e.getAttribute('href'); + }} + while (e) {{ + if (res.linkTitle == '' && e.tagName == 'A') + res.linkTitle = e.text; + if (res.linkUrl == '' && e.tagName == 'A') + res.linkUrl = e.getAttribute('href'); + if (res.mediaUrl == '' && isMediaElement(e)) {{ + res.mediaUrl = e.currentSrc; + res.mediaPaused = e.paused; + res.mediaMuted = e.muted; + }} + e = e.parentElement; + }} + return res; + }})() + """.format(pos.x(), pos.y()) + self.__populate(page.url(), page.execJavaScript(script)) + + def alternateText(self): + """ + Public method to get the alternate text. + + @return alternate text + @rtype str + """ + return self.__alternateText + + def boundingRect(self): + """ + Public method to get the bounding rectangle. + + @return bounding rectangle + @rtype QRect + """ + return QRect(self.__boundingRect) + + def imageUrl(self): + """ + Public method to get the URL of an image. + + @return image URL + @rtype QUrl + """ + return self.__imageUrl + + def isContentEditable(self): + """ + Public method to check for editable content. + + @return flag indicating editable content + @rtype bool + """ + return self.__isContentEditable + + def isContentSelected(self): + """ + Public method to check for selected content. + + @return flag indicating selected content + @rtype bool + """ + return self.__isContentSelected + + def isNull(self): + """ + Public method to test, if the hit test is empty. + + @return flag indicating an empty object + @rtype bool + """ + return self.__isNull + + def linkTitle(self): + """ + Public method to get the title for a link element. + + @return title for a link element + @rtype str + """ + return self.__linkTitle + + def linkUrl(self): + """ + Public method to get the URL for a link element. + + @return URL for a link element + @rtype QUrl + """ + return self.__linkUrl + + def mediaUrl(self): + """ + Public method to get the URL for a media element. + + @return URL for a media element + @rtype QUrl + """ + return self.__mediaUrl + + def mediaPaused(self): + """ + Public method to check, if a media element is paused. + + @return flag indicating a paused media element + @rtype bool + """ + return self.__isMediaPaused + + def mediaMuted(self): + """ + Public method to check, if a media element is muted. + + @return flag indicating a muted media element + @rtype bool + """ + return self.__isMediaMuted + + def pos(self): + """ + Public method to get the position of the hit test. + + @return position of hit test + @rtype QPoint + """ + return QPoint(self.__pos) + + def tagName(self): + """ + Public method to get the name of the tested tag. + + @return name of the tested tag + @rtype str + """ + return self.__tagName + + def __populate(self, url, res): + """ + Private method to populate the object. + + @param url URL of the tested page + @type QUrl + @param res dictionary with result data from JavaScript + @type dict + """ + if not res: + return + + self.__alternateText = res["alternateText"] + self.__imageUrl = QUrl(res["imageUrl"]) + self.__isContentEditable = res["contentEditable"] + self.__isContentSelected = res["contentSelected"] + self.__linkTitle = res["linkTitle"] + self.__linkUrl = QUrl(res["linkUrl"]) + self.__mediaUrl = QUrl(res["mediaUrl"]) + self.__isMediaPaused = res["mediaPaused"] + self.__isMediaMuted = res["mediaMuted"] + self.__tagName = res["tagName"] + + rect = res["boundingRect"] + if len(rect) == 4: + self.__boundingRect = QRect(int(rect[0]), int(rect[1]), + int(rect[2]), int(rect[3])) + + if not self.__imageUrl.isEmpty(): + self.__imageUrl = url.resolved(self.__imageUrl) + if not self.__linkUrl.isEmpty(): + self.__linkUrl = url.resolved(self.__linkUrl) + if not self.__mediaUrl.isEmpty(): + self.__mediaUrl = url.resolved(self.__mediaUrl)
--- a/WebBrowser/WebBrowserPage.py Sun Feb 07 19:36:07 2016 +0100 +++ b/WebBrowser/WebBrowserPage.py Mon Feb 08 20:56:16 2016 +0100 @@ -34,6 +34,8 @@ from .JavaScript.ExternalJsObject import ExternalJsObject +from .Tools.WebHitTestResult import WebHitTestResult + import Preferences import UI.PixmapCache import Globals @@ -721,14 +723,14 @@ """ loop = QEventLoop() resultDict = {"res": None} + QTimer.singleShot(500, loop.quit); def resultCallback(res, resDict=resultDict): if loop and loop.isRunning(): resDict["res"] = res loop.quit() - self.previewView.page().runJavaScript( - script, resultCallback) + self.runJavaScript(script, resultCallback) loop.exec_() return resultDict["res"] @@ -749,15 +751,14 @@ def hitTestContent(self, pos): """ - Public method to test the contents at a given position. + Public method to test the content at a specified position. - @param pos position to be tested + @param pos position to execute the test at @type QPoint - @return object containing the test results - @rtype WebBrowserHitTestResult + @return test result object + @rtype WebHitTestResult """ - # TODO: WebBrowserHitTestResult -## return WebBrowserHitTestResult(self, pos) + return WebHitTestResult(self, pos) def setupWebChannel(self): """
--- a/WebBrowser/WebBrowserView.py Sun Feb 07 19:36:07 2016 +0100 +++ b/WebBrowser/WebBrowserView.py Mon Feb 08 20:56:16 2016 +0100 @@ -18,7 +18,7 @@ QUrl, QBuffer, QIODevice, QFileInfo, Qt, QTimer, QEvent, \ QRect, QFile, QPoint, QByteArray, qVersion from PyQt5.QtGui import QDesktopServices, QClipboard, QMouseEvent, QColor, \ - QPalette, QIcon + QPalette, QIcon, QContextMenuEvent from PyQt5.QtWidgets import qApp, QStyle, QMenu, QApplication, QInputDialog, \ QLineEdit, QLabel, QToolTip, QFrame, QDialog from PyQt5.QtPrintSupport import QPrinter, QPrintDialog @@ -556,6 +556,50 @@ def contextMenuEvent(self, evt): """ + Public method called to create a context menu. + + This method is overridden from QWebEngineView. + + @param evt reference to the context menu event object + (QContextMenuEvent) + """ + pos = evt.pos() + reason = evt.reason() + QTimer.singleShot( + 0, + lambda: self._contextMenuEvent(QContextMenuEvent(reason, pos))) + # needs to be done this way because contextMenuEvent is blocking + # the main loop + + # TODO: re-arrange the menu creation stuff to better adjust to the current situation +##void TabbedWebView::_contextMenuEvent(QContextMenuEvent *event) +##{ +## m_menu->clear(); +## +## const WebHitTestResult hitTest = page()->hitTestContent(event->pos()); +## +## createContextMenu(m_menu, hitTest); +## +## if (!hitTest.isContentEditable() && !hitTest.isContentSelected() && m_window) { +## m_menu->addAction(m_window->adBlockIcon()->menuAction()); +## } +## +## m_menu->addSeparator(); +## m_menu->addAction(tr("Inspect Element"), this, SLOT(inspectElement())); +## +## if (!m_menu->isEmpty()) { +## // Prevent choosing first option with double rightclick +## const QPoint pos = event->globalPos(); +## QPoint p(pos.x(), pos.y() + 1); +## +## m_menu->popup(p); +## return; +## } +## +## WebView::_contextMenuEvent(event); +##} + def _contextMenuEvent(self, evt): + """ Protected method called to create a context menu. This method is overridden from QWebEngineView. @@ -563,6 +607,8 @@ @param evt reference to the context menu event object (QContextMenuEvent) """ + hitTest = self.page().hitTestContent(evt.pos()) + # TODO: User Agent ## from .UserAgent.UserAgentMenu import UserAgentMenu menu = QMenu(self)
--- a/WebBrowser/WebBrowserWindow.py Sun Feb 07 19:36:07 2016 +0100 +++ b/WebBrowser/WebBrowserWindow.py Mon Feb 08 20:56:16 2016 +0100 @@ -129,9 +129,9 @@ self.__isPrivate = private -## self.__eventMouseButtons = Qt.NoButton -## self.__eventKeyboardModifiers = Qt.NoModifier -## + self.__eventMouseButtons = Qt.NoButton + self.__eventKeyboardModifiers = Qt.NoModifier + if self.__initShortcutsOnly: self.__initActions() else:
--- a/eric6.e4p Sun Feb 07 19:36:07 2016 +0100 +++ b/eric6.e4p Mon Feb 08 20:56:16 2016 +0100 @@ -26,54 +26,6 @@ <Source>DataViews/PyCoverageDialog.py</Source> <Source>DataViews/PyProfileDialog.py</Source> <Source>DataViews/__init__.py</Source> - <Source>DebugClients/Python/AsyncFile.py</Source> - <Source>DebugClients/Python/AsyncIO.py</Source> - <Source>DebugClients/Python/DCTestResult.py</Source> - <Source>DebugClients/Python/DebugBase.py</Source> - <Source>DebugClients/Python/DebugClient.py</Source> - <Source>DebugClients/Python/DebugClientBase.py</Source> - <Source>DebugClients/Python/DebugClientCapabilities.py</Source> - <Source>DebugClients/Python/DebugClientThreads.py</Source> - <Source>DebugClients/Python/DebugConfig.py</Source> - <Source>DebugClients/Python/DebugProtocol.py</Source> - <Source>DebugClients/Python/DebugThread.py</Source> - <Source>DebugClients/Python/FlexCompleter.py</Source> - <Source>DebugClients/Python/PyProfile.py</Source> - <Source>DebugClients/Python/__init__.py</Source> - <Source>DebugClients/Python/coverage/__init__.py</Source> - <Source>DebugClients/Python/coverage/__main__.py</Source> - <Source>DebugClients/Python/coverage/annotate.py</Source> - <Source>DebugClients/Python/coverage/backunittest.py</Source> - <Source>DebugClients/Python/coverage/backward.py</Source> - <Source>DebugClients/Python/coverage/bytecode.py</Source> - <Source>DebugClients/Python/coverage/cmdline.py</Source> - <Source>DebugClients/Python/coverage/collector.py</Source> - <Source>DebugClients/Python/coverage/config.py</Source> - <Source>DebugClients/Python/coverage/control.py</Source> - <Source>DebugClients/Python/coverage/data.py</Source> - <Source>DebugClients/Python/coverage/debug.py</Source> - <Source>DebugClients/Python/coverage/env.py</Source> - <Source>DebugClients/Python/coverage/execfile.py</Source> - <Source>DebugClients/Python/coverage/files.py</Source> - <Source>DebugClients/Python/coverage/html.py</Source> - <Source>DebugClients/Python/coverage/misc.py</Source> - <Source>DebugClients/Python/coverage/monkey.py</Source> - <Source>DebugClients/Python/coverage/parser.py</Source> - <Source>DebugClients/Python/coverage/phystokens.py</Source> - <Source>DebugClients/Python/coverage/pickle2json.py</Source> - <Source>DebugClients/Python/coverage/plugin.py</Source> - <Source>DebugClients/Python/coverage/plugin_support.py</Source> - <Source>DebugClients/Python/coverage/python.py</Source> - <Source>DebugClients/Python/coverage/pytracer.py</Source> - <Source>DebugClients/Python/coverage/report.py</Source> - <Source>DebugClients/Python/coverage/results.py</Source> - <Source>DebugClients/Python/coverage/summary.py</Source> - <Source>DebugClients/Python/coverage/templite.py</Source> - <Source>DebugClients/Python/coverage/test_helpers.py</Source> - <Source>DebugClients/Python/coverage/version.py</Source> - <Source>DebugClients/Python/coverage/xmlreport.py</Source> - <Source>DebugClients/Python/eric6dbgstub.py</Source> - <Source>DebugClients/Python/getpass.py</Source> <Source>DebugClients/Python3/AsyncFile.py</Source> <Source>DebugClients/Python3/AsyncIO.py</Source> <Source>DebugClients/Python3/DCTestResult.py</Source> @@ -123,6 +75,54 @@ <Source>DebugClients/Python3/coverage/xmlreport.py</Source> <Source>DebugClients/Python3/eric6dbgstub.py</Source> <Source>DebugClients/Python3/getpass.py</Source> + <Source>DebugClients/Python/AsyncFile.py</Source> + <Source>DebugClients/Python/AsyncIO.py</Source> + <Source>DebugClients/Python/DCTestResult.py</Source> + <Source>DebugClients/Python/DebugBase.py</Source> + <Source>DebugClients/Python/DebugClient.py</Source> + <Source>DebugClients/Python/DebugClientBase.py</Source> + <Source>DebugClients/Python/DebugClientCapabilities.py</Source> + <Source>DebugClients/Python/DebugClientThreads.py</Source> + <Source>DebugClients/Python/DebugConfig.py</Source> + <Source>DebugClients/Python/DebugProtocol.py</Source> + <Source>DebugClients/Python/DebugThread.py</Source> + <Source>DebugClients/Python/FlexCompleter.py</Source> + <Source>DebugClients/Python/PyProfile.py</Source> + <Source>DebugClients/Python/__init__.py</Source> + <Source>DebugClients/Python/coverage/__init__.py</Source> + <Source>DebugClients/Python/coverage/__main__.py</Source> + <Source>DebugClients/Python/coverage/annotate.py</Source> + <Source>DebugClients/Python/coverage/backunittest.py</Source> + <Source>DebugClients/Python/coverage/backward.py</Source> + <Source>DebugClients/Python/coverage/bytecode.py</Source> + <Source>DebugClients/Python/coverage/cmdline.py</Source> + <Source>DebugClients/Python/coverage/collector.py</Source> + <Source>DebugClients/Python/coverage/config.py</Source> + <Source>DebugClients/Python/coverage/control.py</Source> + <Source>DebugClients/Python/coverage/data.py</Source> + <Source>DebugClients/Python/coverage/debug.py</Source> + <Source>DebugClients/Python/coverage/env.py</Source> + <Source>DebugClients/Python/coverage/execfile.py</Source> + <Source>DebugClients/Python/coverage/files.py</Source> + <Source>DebugClients/Python/coverage/html.py</Source> + <Source>DebugClients/Python/coverage/misc.py</Source> + <Source>DebugClients/Python/coverage/monkey.py</Source> + <Source>DebugClients/Python/coverage/parser.py</Source> + <Source>DebugClients/Python/coverage/phystokens.py</Source> + <Source>DebugClients/Python/coverage/pickle2json.py</Source> + <Source>DebugClients/Python/coverage/plugin.py</Source> + <Source>DebugClients/Python/coverage/plugin_support.py</Source> + <Source>DebugClients/Python/coverage/python.py</Source> + <Source>DebugClients/Python/coverage/pytracer.py</Source> + <Source>DebugClients/Python/coverage/report.py</Source> + <Source>DebugClients/Python/coverage/results.py</Source> + <Source>DebugClients/Python/coverage/summary.py</Source> + <Source>DebugClients/Python/coverage/templite.py</Source> + <Source>DebugClients/Python/coverage/test_helpers.py</Source> + <Source>DebugClients/Python/coverage/version.py</Source> + <Source>DebugClients/Python/coverage/xmlreport.py</Source> + <Source>DebugClients/Python/eric6dbgstub.py</Source> + <Source>DebugClients/Python/getpass.py</Source> <Source>DebugClients/__init__.py</Source> <Source>Debugger/BreakPointModel.py</Source> <Source>Debugger/BreakPointViewer.py</Source> @@ -1273,6 +1273,7 @@ <Source>WebBrowser/SearchWidget.py</Source> <Source>WebBrowser/Tools/Scripts.py</Source> <Source>WebBrowser/Tools/WebBrowserTools.py</Source> + <Source>WebBrowser/Tools/WebHitTestResult.py</Source> <Source>WebBrowser/Tools/WebIconLoader.py</Source> <Source>WebBrowser/Tools/WebIconProvider.py</Source> <Source>WebBrowser/Tools/__init__.py</Source> @@ -1726,14 +1727,14 @@ <Interfaces/> <Others> <Other>.hgignore</Other> - <Other>APIs/Python/zope-2.10.7.api</Other> - <Other>APIs/Python/zope-2.11.2.api</Other> - <Other>APIs/Python/zope-3.3.1.api</Other> <Other>APIs/Python3/PyQt4.bas</Other> <Other>APIs/Python3/PyQt5.bas</Other> <Other>APIs/Python3/QScintilla2.bas</Other> <Other>APIs/Python3/eric6.api</Other> <Other>APIs/Python3/eric6.bas</Other> + <Other>APIs/Python/zope-2.10.7.api</Other> + <Other>APIs/Python/zope-2.11.2.api</Other> + <Other>APIs/Python/zope-3.3.1.api</Other> <Other>APIs/QSS/qss.api</Other> <Other>APIs/Ruby/Ruby-1.8.7.api</Other> <Other>APIs/Ruby/Ruby-1.8.7.bas</Other> @@ -1742,8 +1743,8 @@ <Other>CSSs</Other> <Other>CodeTemplates</Other> <Other>DTDs</Other> + <Other>DebugClients/Python3/coverage/doc</Other> <Other>DebugClients/Python/coverage/doc</Other> - <Other>DebugClients/Python3/coverage/doc</Other> <Other>DesignerTemplates</Other> <Other>Dictionaries</Other> <Other>Documentation/Help</Other>