|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2016 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a scheme handler for the eric: scheme. |
|
8 """ |
|
9 |
|
10 from PyQt5.QtCore import pyqtSignal, QBuffer, QIODevice, QUrlQuery, QMutex |
|
11 from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler |
|
12 |
|
13 from E5Gui.E5Application import e5App |
|
14 |
|
15 from E5Utilities.E5MutexLocker import E5MutexLocker |
|
16 |
|
17 from ..Tools.WebBrowserTools import ( |
|
18 getHtmlPage, getJavascript, pixmapFileToDataUrl |
|
19 ) |
|
20 |
|
21 _SupportedPages = [ |
|
22 "adblock", # error page for URLs blocked by AdBlock |
|
23 "home", "start", "startpage", # eric home page |
|
24 "speeddial", # eric speeddial |
|
25 ] |
|
26 |
|
27 |
|
28 class EricSchemeHandler(QWebEngineUrlSchemeHandler): |
|
29 """ |
|
30 Class implementing a scheme handler for the eric: scheme. |
|
31 """ |
|
32 def __init__(self, parent=None): |
|
33 """ |
|
34 Constructor |
|
35 |
|
36 @param parent reference to the parent object |
|
37 @type QObject |
|
38 """ |
|
39 super().__init__(parent) |
|
40 |
|
41 self.__replies = [] |
|
42 |
|
43 def requestStarted(self, job): |
|
44 """ |
|
45 Public method handling the URL request. |
|
46 |
|
47 @param job URL request job |
|
48 @type QWebEngineUrlRequestJob |
|
49 """ |
|
50 reply = EricSchemeReply(job) |
|
51 reply.closed.connect(lambda: self.__replyClosed(reply)) |
|
52 self.__replies.append(reply) |
|
53 job.reply(b"text/html", reply) |
|
54 |
|
55 def __replyClosed(self, reply): |
|
56 """ |
|
57 Private slot handling the closed signal of a reply. |
|
58 |
|
59 @param reply reference to the network reply |
|
60 @type EricSchemeReply |
|
61 """ |
|
62 if reply in self.__replies: |
|
63 self.__replies.remove(reply) |
|
64 |
|
65 |
|
66 class EricSchemeReply(QIODevice): |
|
67 """ |
|
68 Class implementing a reply for a requested eric: page. |
|
69 |
|
70 @signal closed emitted to signal that the web engine has read |
|
71 the data |
|
72 """ |
|
73 closed = pyqtSignal() |
|
74 |
|
75 _speedDialPage = "" |
|
76 |
|
77 def __init__(self, job, parent=None): |
|
78 """ |
|
79 Constructor |
|
80 |
|
81 @param job reference to the URL request |
|
82 @type QWebEngineUrlRequestJob |
|
83 @param parent reference to the parent object |
|
84 @type QObject |
|
85 """ |
|
86 super().__init__(parent) |
|
87 |
|
88 self.__loaded = False |
|
89 self.__job = job |
|
90 self.__mutex = QMutex() |
|
91 |
|
92 self.__pageName = self.__job.requestUrl().path() |
|
93 self.__buffer = QBuffer() |
|
94 |
|
95 self.__loadPage() |
|
96 |
|
97 def __loadPage(self): |
|
98 """ |
|
99 Private method to load the requested page. |
|
100 """ |
|
101 if self.__loaded: |
|
102 return |
|
103 |
|
104 with E5MutexLocker(self.__mutex): |
|
105 if self.__pageName == "adblock": |
|
106 contents = self.__adBlockPage() |
|
107 elif self.__pageName in ["home", "start", "startpage"]: |
|
108 contents = self.__startPage() |
|
109 elif self.__pageName == "speeddial": |
|
110 contents = self.__speedDialPage() |
|
111 else: |
|
112 contents = self.__errorPage() |
|
113 |
|
114 self.__buffer.setData(contents.encode("utf-8")) |
|
115 self.__buffer.open(QIODevice.OpenModeFlag.ReadOnly) |
|
116 self.open(QIODevice.OpenModeFlag.ReadOnly) |
|
117 |
|
118 self.readyRead.emit() |
|
119 |
|
120 self.__loaded = True |
|
121 |
|
122 def bytesAvailable(self): |
|
123 """ |
|
124 Public method to get the number of available bytes. |
|
125 |
|
126 @return number of available bytes |
|
127 @rtype int |
|
128 """ |
|
129 with E5MutexLocker(self.__mutex): |
|
130 return self.__buffer.bytesAvailable() |
|
131 |
|
132 def readData(self, maxlen): |
|
133 """ |
|
134 Public method to retrieve data from the reply object. |
|
135 |
|
136 @param maxlen maximum number of bytes to read (integer) |
|
137 @return string containing the data (bytes) |
|
138 """ |
|
139 with E5MutexLocker(self.__mutex): |
|
140 return self.__buffer.read(maxlen) |
|
141 |
|
142 def close(self): |
|
143 """ |
|
144 Public method used to cloase the reply. |
|
145 """ |
|
146 super().close() |
|
147 self.closed.emit() |
|
148 |
|
149 def __adBlockPage(self): |
|
150 """ |
|
151 Private method to build the AdBlock page. |
|
152 |
|
153 @return built AdBlock page |
|
154 @rtype str |
|
155 """ |
|
156 query = QUrlQuery(self.__job.requestUrl()) |
|
157 rule = query.queryItemValue("rule") |
|
158 subscription = query.queryItemValue("subscription") |
|
159 title = self.tr("Content blocked by AdBlock Plus") |
|
160 message = self.tr( |
|
161 "Blocked by rule: <i>{0} ({1})</i>").format(rule, subscription) |
|
162 |
|
163 page = getHtmlPage("adblockPage.html") |
|
164 page = page.replace( |
|
165 "@FAVICON@", pixmapFileToDataUrl("adBlockPlus16.png", True)) |
|
166 page = page.replace( |
|
167 "@IMAGE@", pixmapFileToDataUrl("adBlockPlus64.png", True)) |
|
168 page = page.replace("@TITLE@", title) |
|
169 page = page.replace("@MESSAGE@", message) |
|
170 |
|
171 return page |
|
172 |
|
173 def __errorPage(self): |
|
174 """ |
|
175 Private method to build the Error page. |
|
176 |
|
177 @return built Error page |
|
178 @rtype str |
|
179 """ |
|
180 page = getHtmlPage("ericErrorPage.html") |
|
181 page = page.replace( |
|
182 "@FAVICON@", pixmapFileToDataUrl("ericWeb16.png", True)) |
|
183 page = page.replace( |
|
184 "@IMAGE@", pixmapFileToDataUrl("ericWeb32.png", True)) |
|
185 page = page.replace( |
|
186 "@TITLE@", self.tr("Error accessing eric: URL")) |
|
187 page = page.replace( |
|
188 "@MESSAGE@", self.tr( |
|
189 "The special URL <strong>{0}</strong> is not supported." |
|
190 " Please use one of these." |
|
191 ).format(self.__job.requestUrl().toDisplayString()) |
|
192 ) |
|
193 page = page.replace( |
|
194 "@ERICLIST@", "<br/>".join([ |
|
195 '<a href="eric:{0}">{0}</a>'.format(u) |
|
196 for u in sorted(_SupportedPages) |
|
197 ]) |
|
198 ) |
|
199 |
|
200 return page |
|
201 |
|
202 def __startPage(self): |
|
203 """ |
|
204 Private method to build the Start page. |
|
205 |
|
206 @return built Start page |
|
207 @rtype str |
|
208 """ |
|
209 page = getHtmlPage("startPage.html") |
|
210 page = page.replace( |
|
211 "@FAVICON@", pixmapFileToDataUrl("ericWeb16.png", True)) |
|
212 page = page.replace( |
|
213 "@IMAGE@", pixmapFileToDataUrl("ericWeb32.png", True)) |
|
214 page = page.replace( |
|
215 "@TITLE@", self.tr("Welcome to eric Web Browser!")) |
|
216 page = page.replace("@ERIC_LINK@", self.tr("About eric")) |
|
217 page = page.replace("@HEADER_TITLE@", self.tr("eric Web Browser")) |
|
218 page = page.replace("@SUBMIT@", self.tr("Search!")) |
|
219 ltr = "LTR" if e5App().isLeftToRight() else"RTL" |
|
220 page = page.replace("@QT_LAYOUT_DIRECTION@", ltr) |
|
221 |
|
222 return page |
|
223 |
|
224 def __speedDialPage(self): |
|
225 """ |
|
226 Private method to create the Speeddial page. |
|
227 |
|
228 @return prepared speeddial page |
|
229 @rtype str |
|
230 """ |
|
231 if not self._speedDialPage: |
|
232 page = getHtmlPage("speeddialPage.html") |
|
233 page = page.replace( |
|
234 "@FAVICON@", pixmapFileToDataUrl("ericWeb16.png", True)) |
|
235 page = page.replace( |
|
236 "@IMG_PLUS@", pixmapFileToDataUrl("plus.png", True)) |
|
237 page = page.replace( |
|
238 "@IMG_CLOSE@", pixmapFileToDataUrl("close.png", True)) |
|
239 page = page.replace( |
|
240 "@IMG_EDIT@", pixmapFileToDataUrl("edit.png", True)) |
|
241 page = page.replace( |
|
242 "@IMG_RELOAD@", pixmapFileToDataUrl("reload.png", True)) |
|
243 page = page.replace( |
|
244 "@IMG_SETTINGS@", pixmapFileToDataUrl("setting.png", True)) |
|
245 page = page.replace( |
|
246 "@LOADING-IMG@", pixmapFileToDataUrl("loading.gif", True)) |
|
247 page = page.replace( |
|
248 "@BOX-BORDER@", |
|
249 pixmapFileToDataUrl("box-border-small.png", True)) |
|
250 |
|
251 page = page.replace("@JQUERY@", getJavascript("jquery.js")) |
|
252 page = page.replace("@JQUERY-UI@", getJavascript("jquery-ui.js")) |
|
253 |
|
254 page = page.replace("@SITE-TITLE@", self.tr("Speed Dial")) |
|
255 page = page.replace("@URL@", self.tr("URL")) |
|
256 page = page.replace("@TITLE@", self.tr("Title")) |
|
257 page = page.replace("@APPLY@", self.tr("Apply")) |
|
258 page = page.replace("@CLOSE@", self.tr("Close")) |
|
259 page = page.replace("@NEW-PAGE@", self.tr("New Page")) |
|
260 page = page.replace("@TITLE-EDIT@", self.tr("Edit")) |
|
261 page = page.replace("@TITLE-REMOVE@", self.tr("Remove")) |
|
262 page = page.replace("@TITLE-RELOAD@", self.tr("Reload")) |
|
263 page = page.replace("@TITLE-WARN@", |
|
264 self.tr("Are you sure to remove this" |
|
265 " speed dial?")) |
|
266 page = page.replace("@TITLE-WARN-REL@", |
|
267 self.tr("Are you sure you want to reload" |
|
268 " all speed dials?")) |
|
269 page = page.replace("@TITLE-FETCHTITLE@", |
|
270 self.tr("Load title from page")) |
|
271 page = page.replace("@SETTINGS-TITLE@", |
|
272 self.tr("Speed Dial Settings")) |
|
273 page = page.replace("@ADD-TITLE@", self.tr("Add New Page")) |
|
274 page = page.replace("@TXT_NRROWS@", |
|
275 self.tr("Maximum pages in a row:")) |
|
276 page = page.replace("@TXT_SDSIZE@", |
|
277 self.tr("Change size of pages:")) |
|
278 page = page.replace("@JAVASCRIPT_DISABLED@", |
|
279 self.tr("SpeedDial requires enabled" |
|
280 " JavaScript.")) |
|
281 |
|
282 self._speedDialPage = page |
|
283 |
|
284 from WebBrowser.WebBrowserWindow import WebBrowserWindow |
|
285 dial = WebBrowserWindow.speedDial() |
|
286 page = ( |
|
287 self._speedDialPage |
|
288 .replace("@INITIAL-SCRIPT@", dial.initialScript()) |
|
289 .replace("@ROW-PAGES@", str(dial.pagesInRow())) |
|
290 .replace("@SD-SIZE@", str(dial.sdSize())) |
|
291 ) |
|
292 |
|
293 return page |