Tue, 02 Mar 2021 17:17:09 +0100

Detlev Offenbach <>
Tue, 02 Mar 2021 17:17:09 +0100
changeset 8143
parent 7923
child 8176
child 8218

Changed the use of PyQt enums because the way they were used previously is deprecated since two years and replaced some deprecated Qt stuff.

# -*- coding: utf-8 -*-

# Copyright (c) 2009 - 2021 Detlev Offenbach <>

Module implementing a scheme access handler for QtHelp.

import mimetypes
import os

from PyQt5.QtCore import (
    pyqtSignal, QByteArray, QIODevice, QBuffer, QMutex
from PyQt5.QtWebEngineCore import (
    QWebEngineUrlSchemeHandler, QWebEngineUrlRequestJob

from E5Utilities.E5MutexLocker import E5MutexLocker

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):
        @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))
            job.reply(reply.mimeType(), reply)
    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:

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):
        @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)
    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)
            data = QByteArray(
                """<head><title>Error 404...</title></head>"""
                """<body><div align="center"><br><br>"""
                """<h1>The page could not be found</h1><br>"""
        with E5MutexLocker(self.__mutex):
    def bytesAvailable(self):
        Public method to get the number of available bytes.
        @return number of available bytes
        @rtype int
        with E5MutexLocker(self.__mutex):
            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)
        with E5MutexLocker(self.__mutex):
    def close(self):
        Public method used to cloase the reply.
        super(QtHelpSchemeReply, self).close()
    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]
            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")

eric ide