eric6/Helpviewer/SpeedDial/SpeedDial.py

branch
maintenance
changeset 7286
7eb04391adf7
parent 7226
babe80d84a3e
parent 7285
1ff497f33f31
child 7287
1c17f2191bdd
equal deleted inserted replaced
7226:babe80d84a3e 7286:7eb04391adf7
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.QtWebKitWidgets import QWebPage
21
22 from E5Gui import E5MessageBox
23
24 from Utilities.AutoSaver import AutoSaver
25 import Utilities
26
27
28 class SpeedDial(QObject):
29 """
30 Class implementing the speed dial.
31
32 @signal pagesChanged() emitted after the list of pages changed
33 @signal speedDialSaved() emitted after the speed dial data was saved
34 """
35 pagesChanged = pyqtSignal()
36 speedDialSaved = pyqtSignal()
37
38 def __init__(self, parent=None):
39 """
40 Constructor
41
42 @param parent reference to the parent object (QObject)
43 """
44 super(SpeedDial, self).__init__(parent)
45
46 self.__regenerateScript = True
47
48 self.__webPages = []
49 self.__webFrames = []
50
51 self.__initialScript = ""
52 self.__thumbnailsDirectory = ""
53
54 self.__thumbnailers = []
55
56 self.__initialize()
57
58 self.pagesChanged.connect(self.__pagesChanged)
59
60 self.__saveTimer = AutoSaver(self, self.save)
61 self.pagesChanged.connect(self.__saveTimer.changeOccurred)
62
63 def addWebFrame(self, frame):
64 """
65 Public method to add a web frame.
66
67 @param frame reference to the frame to be added (QWebFrame)
68 """
69 if frame not in self.__webFrames:
70 self.__webFrames.append(frame)
71
72 def addPage(self, url, title):
73 """
74 Public method to add a page for the given data.
75
76 @param url URL of the page (QUrl)
77 @param title title of the page (string)
78 """
79 if url.isEmpty():
80 return
81
82 from .Page import Page
83 page = Page(url.toString(), title)
84 self.__webPages.append(page)
85
86 self.pagesChanged.emit()
87
88 def removePage(self, url):
89 """
90 Public method to remove a page.
91
92 @param url URL of the page (QUrl)
93 """
94 page = self.pageForUrl(url)
95 if not page.url:
96 return
97
98 self.removeImageForUrl(page.url)
99 self.__webPages.remove(page)
100
101 self.pagesChanged.emit()
102
103 def __imageFileName(self, url):
104 """
105 Private method to generate the image file name for a URL.
106
107 @param url URL to generate the file name from (string)
108 @return name of the image file (string)
109 """
110 return os.path.join(
111 self.__thumbnailsDirectory,
112 str(QCryptographicHash.hash(QByteArray(url.encode("utf-8")),
113 QCryptographicHash.Md5).toHex(), encoding="utf-8") + ".png")
114
115 def initialScript(self):
116 """
117 Public method to get the 'initial' JavaScript script.
118
119 @return initial JavaScript script (string)
120 """
121 if self.__regenerateScript:
122 self.__regenerateScript = False
123 self.__initialScript = ""
124
125 for page in self.__webPages:
126 if page.broken:
127 imgSource = "qrc:icons/brokenPage.png"
128 else:
129 imgSource = self.__imageFileName(page.url)
130 if not os.path.exists(imgSource):
131 self.loadThumbnail(page.url)
132 imgSource = "qrc:icons/loading.gif"
133
134 if not page.url:
135 imgSource = ""
136 else:
137 imgSource = QUrl.fromLocalFile(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(), "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(), "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:"https://eric-ide.python-projects.org/"|'\
194 'title:"Eric Web Site";'\
195 'url:"https://www.riverbankcomputing.com/"|'\
196 'title:"PyQt Web Site";'\
197 'url:"http://www.qt.io/"|title:"Qt Web Site";'\
198 'url:"http://blog.qt.io/"|title:"Qt Blog";'\
199 'url:"https://www.python.org"|'\
200 'title:"Python Language Website";'\
201 'url:"http://www.google.com"|title:"Google";'
202 self.changed(allPages)
203
204 def save(self):
205 """
206 Public method to save the speed dial configuration.
207 """
208 from .SpeedDialWriter import SpeedDialWriter
209 speedDialFile = self.getFileName()
210 writer = SpeedDialWriter()
211 if not writer.write(speedDialFile, self.__webPages,
212 self.__pagesPerRow, self.__speedDialSize):
213 E5MessageBox.critical(
214 None,
215 self.tr("Saving Speed Dial data"),
216 self.tr(
217 """<p>Speed Dial data could not be saved to"""
218 """ <b>{0}</b></p>""").format(speedDialFile))
219 else:
220 self.speedDialSaved.emit()
221
222 def resetDials(self):
223 """
224 Public method to reset the speed dials to the default values.
225 """
226 ok = E5MessageBox.yesNo(
227 None,
228 self.tr("Reset Speed Dials"),
229 self.tr("""Are you sure you want to reset the speed dials to"""
230 """ the default pages?"""))
231 if ok:
232 speedDialFile = self.getFileName()
233 if os.path.exists(speedDialFile):
234 os.remove(speedDialFile)
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 for page in self.__webPages:
253 if page.url == urlString:
254 return page
255
256 from .Page import Page
257 return Page()
258
259 def urlForShortcut(self, key):
260 """
261 Public method to get the URL for the given shortcut key.
262
263 @param key shortcut key (integer)
264 @return URL for the key (QUrl)
265 """
266 if key < 0 or len(self.__webPages) <= key:
267 return QUrl()
268
269 return QUrl.fromEncoded(self.__webPages[key].url.encode("utf-8"))
270
271 @pyqtSlot(str)
272 def changed(self, allPages):
273 """
274 Public slot to react on changed pages.
275
276 @param allPages string giving all pages (string)
277 """
278 if not allPages:
279 return
280
281 entries = allPages.split('";')
282 self.__webPages = []
283
284 from .Page import Page
285 for entry in entries:
286 if not entry:
287 continue
288
289 tmp = entry.split('"|')
290 if len(tmp) == 2:
291 broken = False
292 elif len(tmp) == 3:
293 broken = "brokenPage" in tmp[2][5:]
294 else:
295 continue
296
297 page = Page(tmp[0][5:], tmp[1][7:], broken)
298 self.__webPages.append(page)
299
300 self.pagesChanged.emit()
301
302 @pyqtSlot(str)
303 @pyqtSlot(str, bool)
304 def loadThumbnail(self, url, loadTitle=False):
305 """
306 Public slot to load a thumbnail of the given URL.
307
308 @param url URL of the thumbnail (string)
309 @param loadTitle flag indicating to get the title for the thumbnail
310 from the site (boolean)
311 """
312 if not url:
313 return
314
315 from .PageThumbnailer import PageThumbnailer
316 thumbnailer = PageThumbnailer(self)
317 thumbnailer.setUrl(QUrl.fromEncoded(url.encode("utf-8")))
318 thumbnailer.setLoadTitle(loadTitle)
319 thumbnailer.thumbnailCreated.connect(
320 lambda imag: self.__thumbnailCreated(imag, thumbnailer))
321 self.__thumbnailers.append(thumbnailer)
322
323 thumbnailer.start()
324
325 @pyqtSlot(str)
326 def removeImageForUrl(self, url):
327 """
328 Public slot to remove the image for a URL.
329
330 @param url URL to remove the image for (string)
331 """
332 fileName = self.__imageFileName(url)
333 if os.path.exists(fileName):
334 os.remove(fileName)
335
336 @pyqtSlot(str, result=str)
337 def urlFromUserInput(self, url):
338 """
339 Public slot to get the URL from user input.
340
341 @param url URL entered by the user (string)
342 @return sanitized URL (string)
343 """
344 return QUrl.fromUserInput(url).toString()
345
346 @pyqtSlot(str, result=str)
347 def unescapeTitle(self, title):
348 """
349 Public slot to unescape the titel string.
350
351 @param title escaped title (string)
352 @return un-escaped title (string)
353 """
354 return Utilities.html_udecode(title)
355
356 @pyqtSlot(int)
357 def setPagesInRow(self, count):
358 """
359 Public slot to set the number of pages per row.
360
361 @param count number of pages per row (integer)
362 """
363 self.__pagesPerRow = count
364 self.__saveTimer.changeOccurred()
365
366 def pagesInRow(self):
367 """
368 Public method to get the number of dials per row.
369
370 @return number of dials per row (integer)
371 """
372 return self.__pagesPerRow
373
374 @pyqtSlot(int)
375 def setSdSize(self, size):
376 """
377 Public slot to set the size of the speed dial.
378
379 @param size size of the speed dial (integer)
380 """
381 self.__speedDialSize = size
382 self.__saveTimer.changeOccurred()
383
384 def sdSize(self):
385 """
386 Public method to get the speed dial size.
387
388 @return speed dial size (integer)
389 """
390 return self.__speedDialSize
391
392 def __thumbnailCreated(self, image, thumbnailer):
393 """
394 Private slot to handle the creation of a thumbnail image.
395
396 @param image thumbnail image
397 @type QPixmap
398 @param thumbnailer reference to the page thumbnailer
399 @type PageThumbnailer
400 """
401 if thumbnailer in self.__thumbnailers:
402 loadTitle = thumbnailer.loadTitle()
403 title = thumbnailer.title()
404 url = thumbnailer.url().toString()
405 fileName = self.__imageFileName(url)
406
407 if image.isNull():
408 fileName = "qrc:icons/brokenPage.png"
409 title = self.tr("Unable to load")
410 loadTitle = True
411 page = self.pageForUrl(thumbnailer.url())
412 page.broken = True
413 else:
414 if not image.save(fileName):
415 qWarning(
416 "SpeedDial.__thumbnailCreated: Cannot save thumbnail"
417 " to {0}".format(fileName))
418
419 fileName = QUrl.fromLocalFile(fileName).toString()
420
421 self.__regenerateScript = True
422
423 for frame in self.__cleanFrames():
424 frame.evaluateJavaScript("setImageToUrl('{0}', '{1}');".format(
425 url, fileName))
426 if loadTitle:
427 frame.evaluateJavaScript(
428 "setTitleToUrl('{0}', '{1}');".format(
429 url, Utilities.html_uencode(title)))
430
431 thumbnailer.deleteLater()
432 self.__thumbnailers.remove(thumbnailer)
433
434 def __cleanFrames(self):
435 """
436 Private method to clean all frames.
437
438 @return list of speed dial frames (list of QWebFrame)
439 """
440 frames = []
441
442 for frame in self.__webFrames[:]:
443 if frame.url().toString() == "eric:speeddial":
444 frames.append(frame)
445 else:
446 self.__webFrames.remove(frame)
447
448 return frames
449
450 def __pagesChanged(self):
451 """
452 Private slot to react on a change of the pages configuration.
453 """
454 # update all speed dial pages
455 self.__regenerateScript = True
456 for frame in self.__cleanFrames():
457 frame.page().triggerAction(QWebPage.Reload)

eric ide

mercurial