src/eric7/WebBrowser/SpeedDial/SpeedDial.py

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

eric ide

mercurial