--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/WebBrowser/Network/QtHelpSchemeHandler.py Sun Apr 14 15:09:21 2019 +0200 @@ -0,0 +1,227 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a scheme access handler for QtHelp. +""" + +from __future__ import unicode_literals + +import mimetypes +import os + +from PyQt5.QtCore import pyqtSignal, QByteArray, QIODevice, QBuffer, QMutex, \ + QMutexLocker +from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler, \ + QWebEngineUrlRequestJob + +QtDocPath = "qthelp://org.qt-project." + +ExtensionMap = { + ".bmp": "image/bmp", + ".css": "text/css", + ".gif": "image/gif", + ".html": "text/html", + ".htm": "text/html", + ".ico": "image/x-icon", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".js": "application/x-javascript", + ".mng": "video/x-mng", + ".pbm": "image/x-portable-bitmap", + ".pgm": "image/x-portable-graymap", + ".pdf": "application/pdf", + ".png": "image/png", + ".ppm": "image/x-portable-pixmap", + ".rss": "application/rss+xml", + ".svg": "image/svg+xml", + ".svgz": "image/svg+xml", + ".text": "text/plain", + ".tif": "image/tiff", + ".tiff": "image/tiff", + ".txt": "text/plain", + ".xbm": "image/x-xbitmap", + ".xml": "text/xml", + ".xpm": "image/x-xpm", + ".xsl": "text/xsl", + ".xhtml": "application/xhtml+xml", + ".wml": "text/vnd.wap.wml", + ".wmlc": "application/vnd.wap.wmlc", +} + + +class QtHelpSchemeHandler(QWebEngineUrlSchemeHandler): + """ + Class implementing a scheme handler for the qthelp: scheme. + """ + def __init__(self, engine, parent=None): + """ + Constructor + + @param engine reference to the help engine + @type QHelpEngine + @param parent reference to the parent object + @type QObject + """ + super(QtHelpSchemeHandler, self).__init__(parent) + + self.__engine = engine + + self.__replies = [] + + def requestStarted(self, job): + """ + Public method handling the URL request. + + @param job URL request job + @type QWebEngineUrlRequestJob + """ + if job.requestUrl().scheme() == "qthelp": + reply = QtHelpSchemeReply(job, self.__engine) + reply.closed.connect(lambda: self.__replyClosed(reply)) + self.__replies.append(reply) + job.reply(reply.mimeType(), reply) + else: + job.fail(QWebEngineUrlRequestJob.UrlInvalid) + + def __replyClosed(self, reply): + """ + Private slot handling the closed signal of a reply. + + @param reply reference to the network reply + @type QtHelpSchemeReply + """ + if reply in self.__replies: + self.__replies.remove(reply) + + +class QtHelpSchemeReply(QIODevice): + """ + Class implementing a reply for a requested qthelp: page. + + @signal closed emitted to signal that the web engine has read + the data + """ + closed = pyqtSignal() + + def __init__(self, job, engine, parent=None): + """ + Constructor + + @param job reference to the URL request + @type QWebEngineUrlRequestJob + @param engine reference to the help engine + @type QHelpEngine + @param parent reference to the parent object + @type QObject + """ + super(QtHelpSchemeReply, self).__init__(parent) + + self.__job = job + self.__engine = engine + self.__mutex = QMutex() + + self.__buffer = QBuffer() + + # determine mimetype + url = self.__job.requestUrl() + strUrl = url.toString() + + # For some reason the url to load maybe wrong (passed from web engine) + # though the css file and the references inside should work that way. + # One possible problem might be that the css is loaded at the same + # level as the html, thus a path inside the css like + # (../images/foo.png) might cd out of the virtual folder + if not self.__engine.findFile(url).isValid(): + if strUrl.startswith(QtDocPath): + newUrl = self.__job.requestUrl() + if not newUrl.path().startswith("/qdoc/"): + newUrl.setPath("/qdoc" + newUrl.path()) + url = newUrl + strUrl = url.toString() + + self.__mimeType = mimetypes.guess_type(strUrl)[0] + if self.__mimeType is None: + # do our own (limited) guessing + self.__mimeType = self.__mimeFromUrl(url) + + self.__loadQtHelpPage(url) + + def __loadQtHelpPage(self, url): + """ + Private method to load the requested QtHelp page. + + @param url URL of the requested page + @type QUrl + """ + if self.__engine.findFile(url).isValid(): + data = self.__engine.fileData(url) + else: + data = QByteArray(self.tr( + """<html>""" + """<head><title>Error 404...</title></head>""" + """<body><div align="center"><br><br>""" + """<h1>The page could not be found</h1><br>""" + """<h3>'{0}'</h3></div></body>""" + """</html>""").format(url.toString()) + .encode("utf-8")) + + lock = QMutexLocker(self.__mutex) + self.__buffer.setData(data) + self.__buffer.open(QIODevice.ReadOnly) + self.open(QIODevice.ReadOnly) + lock.unlock() + + self.readyRead.emit() + + def bytesAvailable(self): + """ + Public method to get the number of available bytes. + + @return number of available bytes + @rtype int + """ + lock = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ + return self.__buffer.bytesAvailable() + + def readData(self, maxlen): + """ + Public method to retrieve data from the reply object. + + @param maxlen maximum number of bytes to read (integer) + @return string containing the data (bytes) + """ + lock = QMutexLocker(self.__mutex) # __IGNORE_WARNING__ + return self.__buffer.read(maxlen) + + def close(self): + """ + Public method used to cloase the reply. + """ + super(QtHelpSchemeReply, self).close() + self.closed.emit() + + def __mimeFromUrl(self, url): + """ + Private method to guess the mime type given an URL. + + @param url URL to guess the mime type from (QUrl) + @return mime type for the given URL (string) + """ + path = url.path() + ext = os.path.splitext(path)[1].lower() + if ext in ExtensionMap: + return ExtensionMap[ext] + else: + return "application/octet-stream" + + def mimeType(self): + """ + Public method to get the reply mime type. + + @return mime type of the reply + @rtype bytes + """ + return self.__mimeType.encode("utf-8")