1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2016 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing a scheme handler for the eric: scheme. 8""" 9 10from PyQt5.QtCore import pyqtSignal, QBuffer, QIODevice, QUrlQuery, QMutex 11from PyQt5.QtWebEngineCore import QWebEngineUrlSchemeHandler 12 13from E5Gui.E5Application import e5App 14 15from E5Utilities.E5MutexLocker import E5MutexLocker 16 17from ..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 28class 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 66class 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 294