src/eric7/QtHelpInterface/QtHelpSchemeHandler.py

branch
eric7
changeset 9686
2eee7a645cba
parent 9653
e67609152c5e
child 10431
64157aeb0312
equal deleted inserted replaced
9685:b43e04854aba 9686:2eee7a645cba
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a scheme access handler for QtHelp.
8 """
9
10 import mimetypes
11 import os
12
13 from PyQt6.QtCore import QBuffer, QByteArray, QIODevice, QMutex, pyqtSignal
14 from PyQt6.QtWebEngineCore import QWebEngineUrlRequestJob, QWebEngineUrlSchemeHandler
15
16 from eric7.EricUtilities.EricMutexLocker import EricMutexLocker
17
18 QtDocPath = "qthelp://org.qt-project."
19
20 ExtensionMap = {
21 ".bmp": "image/bmp",
22 ".css": "text/css",
23 ".gif": "image/gif",
24 ".html": "text/html",
25 ".htm": "text/html",
26 ".ico": "image/x-icon",
27 ".jpeg": "image/jpeg",
28 ".jpg": "image/jpeg",
29 ".js": "application/x-javascript",
30 ".mng": "video/x-mng",
31 ".pbm": "image/x-portable-bitmap",
32 ".pgm": "image/x-portable-graymap",
33 ".pdf": "application/pdf",
34 ".png": "image/png",
35 ".ppm": "image/x-portable-pixmap",
36 ".rss": "application/rss+xml",
37 ".svg": "image/svg+xml",
38 ".svgz": "image/svg+xml",
39 ".text": "text/plain",
40 ".tif": "image/tiff",
41 ".tiff": "image/tiff",
42 ".txt": "text/plain",
43 ".xbm": "image/x-xbitmap",
44 ".xml": "text/xml",
45 ".xpm": "image/x-xpm",
46 ".xsl": "text/xsl",
47 ".xhtml": "application/xhtml+xml",
48 ".wml": "text/vnd.wap.wml",
49 ".wmlc": "application/vnd.wap.wmlc",
50 }
51
52
53 class QtHelpSchemeHandler(QWebEngineUrlSchemeHandler):
54 """
55 Class implementing a scheme handler for the qthelp: scheme.
56 """
57
58 def __init__(self, engine, parent=None):
59 """
60 Constructor
61
62 @param engine reference to the help engine
63 @type QHelpEngine
64 @param parent reference to the parent object
65 @type QObject
66 """
67 super().__init__(parent)
68
69 self.__engine = engine
70
71 self.__replies = []
72
73 def requestStarted(self, job):
74 """
75 Public method handling the URL request.
76
77 @param job URL request job
78 @type QWebEngineUrlRequestJob
79 """
80 if job.requestUrl().scheme() == "qthelp":
81 reply = QtHelpSchemeReply(job, self.__engine)
82 reply.closed.connect(lambda: self.__replyClosed(reply))
83 self.__replies.append(reply)
84 job.reply(reply.mimeType(), reply)
85 else:
86 job.fail(QWebEngineUrlRequestJob.Error.UrlInvalid)
87
88 def __replyClosed(self, reply):
89 """
90 Private slot handling the closed signal of a reply.
91
92 @param reply reference to the network reply
93 @type QtHelpSchemeReply
94 """
95 if reply in self.__replies:
96 self.__replies.remove(reply)
97
98
99 class QtHelpSchemeReply(QIODevice):
100 """
101 Class implementing a reply for a requested qthelp: page.
102
103 @signal closed emitted to signal that the web engine has read
104 the data
105 """
106
107 closed = pyqtSignal()
108
109 def __init__(self, job, engine, parent=None):
110 """
111 Constructor
112
113 @param job reference to the URL request
114 @type QWebEngineUrlRequestJob
115 @param engine reference to the help engine
116 @type QHelpEngine
117 @param parent reference to the parent object
118 @type QObject
119 """
120 super().__init__(parent)
121
122 self.__job = job
123 self.__engine = engine
124 self.__mutex = QMutex()
125
126 self.__buffer = QBuffer()
127
128 # determine mimetype
129 url = self.__job.requestUrl()
130 strUrl = url.toString()
131
132 # For some reason the url to load maybe wrong (passed from web engine)
133 # though the css file and the references inside should work that way.
134 # One possible problem might be that the css is loaded at the same
135 # level as the html, thus a path inside the css like
136 # (../images/foo.png) might cd out of the virtual folder
137 if not self.__engine.findFile(url).isValid() and strUrl.startswith(QtDocPath):
138 newUrl = self.__job.requestUrl()
139 if not newUrl.path().startswith("/qdoc/"):
140 newUrl.setPath("/qdoc" + newUrl.path())
141 url = newUrl
142 strUrl = url.toString()
143
144 self.__mimeType = mimetypes.guess_type(strUrl)[0]
145 if self.__mimeType is None:
146 # do our own (limited) guessing
147 self.__mimeType = self.__mimeFromUrl(url)
148
149 self.__loadQtHelpPage(url)
150
151 def __loadQtHelpPage(self, url):
152 """
153 Private method to load the requested QtHelp page.
154
155 @param url URL of the requested page
156 @type QUrl
157 """
158 data = (
159 self.__engine.fileData(url)
160 if self.__engine.findFile(url).isValid()
161 else QByteArray(
162 self.tr(
163 """<html>"""
164 """<head><title>Error 404...</title></head>"""
165 """<body><div align="center"><br><br>"""
166 """<h1>The page could not be found</h1><br>"""
167 """<h3>'{0}'</h3></div></body>"""
168 """</html>"""
169 )
170 .format(url.toString())
171 .encode("utf-8")
172 )
173 )
174
175 with EricMutexLocker(self.__mutex):
176 self.__buffer.setData(data)
177 self.__buffer.open(QIODevice.OpenModeFlag.ReadOnly)
178 self.open(QIODevice.OpenModeFlag.ReadOnly)
179
180 self.readyRead.emit()
181
182 def bytesAvailable(self):
183 """
184 Public method to get the number of available bytes.
185
186 @return number of available bytes
187 @rtype int
188 """
189 with EricMutexLocker(self.__mutex):
190 return self.__buffer.bytesAvailable()
191
192 def readData(self, maxlen):
193 """
194 Public method to retrieve data from the reply object.
195
196 @param maxlen maximum number of bytes to read (integer)
197 @return string containing the data (bytes)
198 """
199 with EricMutexLocker(self.__mutex):
200 return self.__buffer.read(maxlen)
201
202 def close(self):
203 """
204 Public method used to cloase the reply.
205 """
206 super().close()
207 self.closed.emit()
208
209 def __mimeFromUrl(self, url):
210 """
211 Private method to guess the mime type given an URL.
212
213 @param url URL to guess the mime type from (QUrl)
214 @return mime type for the given URL (string)
215 """
216 path = url.path()
217 ext = os.path.splitext(path)[1].lower()
218 if ext in ExtensionMap:
219 return ExtensionMap[ext]
220 else:
221 return "application/octet-stream"
222
223 def mimeType(self):
224 """
225 Public method to get the reply mime type.
226
227 @return mime type of the reply
228 @rtype bytes
229 """
230 return self.__mimeType.encode("utf-8")

eric ide

mercurial