WebBrowser/SpeedDial/SpeedDial.py

branch
QtWebEngine
changeset 4865
4adc526bc4b3
child 4868
985d275502c8
equal deleted inserted replaced
4864:00a215a67f25 4865:4adc526bc4b3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the speed dial.
8 """
9
10 from __future__ import unicode_literals
11 try:
12 str = unicode
13 except NameError:
14 pass
15
16 import os
17
18 from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QCryptographicHash, \
19 QByteArray, QUrl, qWarning
20 from PyQt5.QtGui import QPixmap
21 from PyQt5.QtWebEngineWidgets import QWebEnginePage
22
23 from E5Gui import E5MessageBox
24
25 from ..Tools.WebBrowserTools import pixmapToDataUrl
26
27 from Utilities.AutoSaver import AutoSaver
28 import Utilities
29
30
31 class SpeedDial(QObject):
32 """
33 Class implementing the speed dial.
34
35 @signal pagesChanged() emitted after the list of pages changed
36 @signal thumbnailLoaded(url, src) emitted after a thumbnail was loaded
37 @signal titleLoaded(url, title) emitted after a title was loaded
38 @signal speedDialSaved() emitted after the speed dial data was saved
39 """
40 pagesChanged = pyqtSignal()
41 thumbnailLoaded = pyqtSignal(str, str)
42 pageTitleLoaded = pyqtSignal(str, str)
43 speedDialSaved = pyqtSignal()
44
45 def __init__(self, parent=None):
46 """
47 Constructor
48
49 @param parent reference to the parent object (QObject)
50 """
51 super(SpeedDial, self).__init__(parent)
52
53 self.__regenerateScript = True
54
55 self.__webPages = []
56
57 self.__initialScript = ""
58 self.__thumbnailsDirectory = ""
59
60 self.__thumbnailers = []
61
62 self.__initialize()
63
64 self.__saveTimer = AutoSaver(self, self.save)
65 self.pagesChanged.connect(self.__saveTimer.changeOccurred)
66
67 def addPage(self, url, title):
68 """
69 Public method to add a page for the given data.
70
71 @param url URL of the page (QUrl)
72 @param title title of the page (string)
73 """
74 if url.isEmpty():
75 return
76
77 from .Page import Page
78 page = Page(
79 self.__escapeUrl(url.toString()),
80 self.__escapeTitle(title))
81 self.__webPages.append(page)
82 self.__regenerateScript = True
83
84 self.pagesChanged.emit()
85
86 def removePage(self, url):
87 """
88 Public method to remove a page.
89
90 @param url URL of the page (QUrl)
91 """
92 page = self.pageForUrl(url)
93 if not page.isValid():
94 return
95
96 self.removeImageForUrl(page.url)
97 self.__webPages.remove(page)
98 self.__regenerateScript = True
99
100 self.pagesChanged.emit()
101
102 def __imageFileName(self, url):
103 """
104 Private method to generate the image file name for a URL.
105
106 @param url URL to generate the file name from (string)
107 @return name of the image file (string)
108 """
109 return os.path.join(
110 self.__thumbnailsDirectory,
111 str(QCryptographicHash.hash(QByteArray(url.encode("utf-8")),
112 QCryptographicHash.Md5).toHex(), encoding="utf-8") + ".png")
113
114 def initialScript(self):
115 """
116 Public method to get the 'initial' JavaScript script.
117
118 @return initial JavaScript script (string)
119 """
120 if self.__regenerateScript:
121 self.__regenerateScript = False
122 self.__initialScript = ""
123
124 for page in self.__webPages:
125 if page.broken:
126 imgSource = "qrc:icons/brokenPage.png"
127 else:
128 imgSource = self.__imageFileName(page.url)
129 if not os.path.exists(imgSource):
130 self.loadThumbnail(page.url)
131 imgSource = "qrc:icons/loading.gif"
132
133 if not page.url:
134 imgSource = ""
135 else:
136 imgSource = \
137 pixmapToDataUrl(QPixmap(imgSource)).toString()
138
139 self.__initialScript += \
140 "addBox('{0}', '{1}', '{2}');\n".format(
141 page.url, Utilities.html_uencode(page.title),
142 imgSource)
143
144 return self.__initialScript
145
146 def getFileName(self):
147 """
148 Public method to get the file name of the user agents file.
149
150 @return name of the user agents file (string)
151 """
152 return os.path.join(
153 Utilities.getConfigDir(), "web_browser", "speedDial.xml")
154
155 def __initialize(self):
156 """
157 Private method to initialize the speed dial.
158 """
159 self.__thumbnailsDirectory = os.path.join(
160 Utilities.getConfigDir(), "web_browser", "thumbnails")
161 # Create directory if it does not exist yet
162 if not os.path.exists(self.__thumbnailsDirectory):
163 os.makedirs(self.__thumbnailsDirectory)
164
165 self.__load()
166
167 def reload(self):
168 """
169 Public method to reload the speed dial data.
170 """
171 self.__load()
172
173 def __load(self):
174 """
175 Private method to load the speed dial configuration.
176 """
177 allPages, pagesPerRow, speedDialSize = [], 0, 0
178
179 speedDialFile = self.getFileName()
180 if os.path.exists(speedDialFile):
181 from .SpeedDialReader import SpeedDialReader
182 reader = SpeedDialReader()
183 allPages, pagesPerRow, speedDialSize = reader.read(speedDialFile)
184
185 self.__pagesPerRow = pagesPerRow if pagesPerRow else 4
186 self.__speedDialSize = speedDialSize if speedDialSize else 231
187
188 if allPages:
189 self.__webPages = allPages
190 self.pagesChanged.emit()
191 else:
192 allPages = \
193 'url:"http://eric-ide.python-projects.org/"|'\
194 'title:"Eric Web Site";'\
195 'url:"http://www.riverbankcomputing.com/"|'\
196 'title:"PyQt Web Site";'\
197 'url:"http://www.qt.io/"|title:"Qt Web Site";'\
198 'url:"http://blog.qt.digia.com/"|title:"Qt Blog";'\
199 'url:"http://www.python.org"|title:"Python Language Website";'\
200 'url:"http://www.google.com"|title:"Google";'
201 self.changed(allPages)
202
203 def save(self):
204 """
205 Public method to save the speed dial configuration.
206 """
207 from .SpeedDialWriter import SpeedDialWriter
208 speedDialFile = self.getFileName()
209 writer = SpeedDialWriter()
210 if not writer.write(speedDialFile, self.__webPages,
211 self.__pagesPerRow, self.__speedDialSize):
212 E5MessageBox.critical(
213 None,
214 self.tr("Saving Speed Dial data"),
215 self.tr(
216 """<p>Speed Dial data could not be saved to"""
217 """ <b>{0}</b></p>""").format(speedDialFile))
218 else:
219 self.speedDialSaved.emit()
220
221 def close(self):
222 """
223 Public method to close the user agents manager.
224 """
225 self.__saveTimer.saveIfNeccessary()
226
227 def pageForUrl(self, url):
228 """
229 Public method to get the page for the given URL.
230
231 @param url URL to be searched for (QUrl)
232 @return page for the URL (Page)
233 """
234 urlString = url.toString()
235 if urlString.endswith("/"):
236 urlString = urlString[:-1]
237
238 for page in self.__webPages:
239 if page.url == urlString:
240 return page
241
242 from .Page import Page
243 return Page()
244
245 def urlForShortcut(self, key):
246 """
247 Public method to get the URL for the given shortcut key.
248
249 @param key shortcut key (integer)
250 @return URL for the key (QUrl)
251 """
252 if key < 0 or len(self.__webPages) <= key:
253 return QUrl()
254
255 return QUrl.fromEncoded(self.__webPages[key].url.encode("utf-8"))
256
257 @pyqtSlot(str)
258 def changed(self, allPages):
259 """
260 Public slot to react on changed pages.
261
262 @param allPages string giving all pages (string)
263 """
264 if not allPages:
265 return
266
267 entries = allPages.split('";')
268 self.__webPages = []
269
270 from .Page import Page
271 for entry in entries:
272 if not entry:
273 continue
274
275 tmp = entry.split('"|')
276 if len(tmp) == 2:
277 broken = False
278 elif len(tmp) == 3:
279 broken = "brokenPage" in tmp[2][5:]
280 else:
281 continue
282
283 url = tmp[0][5:]
284 if url.endswith("/"):
285 url = url[:-1]
286 title = tmp[1][7:]
287 page = Page(url, title, broken)
288 self.__webPages.append(page)
289
290 self.pagesChanged.emit()
291
292 @pyqtSlot(str)
293 @pyqtSlot(str, bool)
294 def loadThumbnail(self, url, loadTitle=False):
295 """
296 Public slot to load a thumbnail of the given URL.
297
298 @param url URL of the thumbnail (string)
299 @param loadTitle flag indicating to get the title for the thumbnail
300 from the site (boolean)
301 """
302 if not url:
303 return
304
305 from .PageThumbnailer import PageThumbnailer
306 thumbnailer = PageThumbnailer(self)
307 thumbnailer.setUrl(QUrl.fromEncoded(url.encode("utf-8")))
308 thumbnailer.setLoadTitle(loadTitle)
309 thumbnailer.thumbnailCreated.connect(self.__thumbnailCreated)
310 self.__thumbnailers.append(thumbnailer)
311
312 thumbnailer.start()
313
314 @pyqtSlot(str)
315 def removeImageForUrl(self, url):
316 """
317 Public slot to remove the image for a URL.
318
319 @param url URL to remove the image for (string)
320 """
321 fileName = self.__imageFileName(url)
322 if os.path.exists(fileName):
323 os.remove(fileName)
324
325 @pyqtSlot(str, result=str)
326 def urlFromUserInput(self, url):
327 """
328 Public slot to get the URL from user input.
329
330 @param url URL entered by the user (string)
331 @return sanitized URL (string)
332 """
333 return QUrl.fromUserInput(url).toString()
334
335 @pyqtSlot(int)
336 def setPagesInRow(self, count):
337 """
338 Public slot to set the number of pages per row.
339
340 @param count number of pages per row (integer)
341 """
342 self.__pagesPerRow = count
343 self.__saveTimer.changeOccurred()
344
345 def pagesInRow(self):
346 """
347 Public method to get the number of dials per row.
348
349 @return number of dials per row (integer)
350 """
351 return self.__pagesPerRow
352
353 @pyqtSlot(int)
354 def setSdSize(self, size):
355 """
356 Public slot to set the size of the speed dial.
357
358 @param size size of the speed dial (integer)
359 """
360 self.__speedDialSize = size
361 self.__saveTimer.changeOccurred()
362
363 def sdSize(self):
364 """
365 Public method to get the speed dial size.
366
367 @return speed dial size (integer)
368 """
369 return self.__speedDialSize
370
371 def __thumbnailCreated(self, image):
372 """
373 Private slot to handle the creation of a thumbnail image.
374
375 @param image thumbnail image (QPixmap)
376 """
377 from .PageThumbnailer import PageThumbnailer
378 thumbnailer = self.sender()
379 if not isinstance(thumbnailer, PageThumbnailer) or \
380 thumbnailer not in self.__thumbnailers:
381 return
382
383 loadTitle = thumbnailer.loadTitle()
384 title = thumbnailer.title()
385 url = thumbnailer.url().toString()
386 fileName = self.__imageFileName(url)
387
388 if image.isNull():
389 fileName = "qrc:icons/brokenPage.png"
390 title = self.tr("Unable to load")
391 loadTitle = True
392 page = self.pageForUrl(thumbnailer.url())
393 page.broken = True
394 else:
395 if not image.save(fileName, "PNG"):
396 qWarning(
397 "SpeedDial.__thumbnailCreated: Cannot save thumbnail"
398 " to {0}".format(fileName))
399
400 self.__regenerateScript = True
401 thumbnailer.deleteLater()
402 self.__thumbnailers.remove(thumbnailer)
403
404 if loadTitle:
405 self.pageTitleLoaded.emit(url, title)
406
407 self.thumbnailLoaded.emit(
408 url, pixmapToDataUrl(QPixmap(fileName)).toString())
409
410 def __escapeTitle(self, title):
411 """
412 Private method to escape a title string.
413
414 @param title title string to be escaped
415 @type str
416 @return escaped title string
417 @rtype str
418 """
419 title = title.replace('"', "&quot;").replace("'", "&apos;")
420 return title
421
422 def __escapeUrl(self, url):
423 """
424 Private method to escape an URL string.
425
426 @param url URL to be escaped
427 @type str
428 @return escaped URL string
429 @rtype str
430 """
431 url = url.replace('"', "").replace("'", "")
432 return url

eric ide

mercurial