eric6/WebBrowser/SpeedDial/SpeedDial.py

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

eric ide

mercurial