eric6/WebBrowser/Bookmarks/BookmarksManager.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the bookmarks manager.
8 """
9
10 from __future__ import unicode_literals
11
12 import os
13
14 from PyQt5.QtCore import pyqtSignal, QT_TRANSLATE_NOOP, QObject, QFile, \
15 QIODevice, QXmlStreamReader, QDateTime, QFileInfo, QUrl, \
16 QCoreApplication
17 from PyQt5.QtWidgets import QUndoStack, QUndoCommand, QDialog
18
19 from E5Gui import E5MessageBox, E5FileDialog
20
21 from .BookmarkNode import BookmarkNode
22
23 from Utilities.AutoSaver import AutoSaver
24 import Utilities
25
26 BOOKMARKBAR = QT_TRANSLATE_NOOP("BookmarksManager", "Bookmarks Bar")
27 BOOKMARKMENU = QT_TRANSLATE_NOOP("BookmarksManager", "Bookmarks Menu")
28
29 StartRoot = 0
30 StartMenu = 1
31 StartToolBar = 2
32
33
34 class BookmarksManager(QObject):
35 """
36 Class implementing the bookmarks manager.
37
38 @signal entryAdded(BookmarkNode) emitted after a bookmark node has been
39 added
40 @signal entryRemoved(BookmarkNode, int, BookmarkNode) emitted after a
41 bookmark node has been removed
42 @signal entryChanged(BookmarkNode) emitted after a bookmark node has been
43 changed
44 @signal bookmarksSaved() emitted after the bookmarks were saved
45 @signal bookmarksReloaded() emitted after the bookmarks were reloaded
46 """
47 entryAdded = pyqtSignal(BookmarkNode)
48 entryRemoved = pyqtSignal(BookmarkNode, int, BookmarkNode)
49 entryChanged = pyqtSignal(BookmarkNode)
50 bookmarksSaved = pyqtSignal()
51 bookmarksReloaded = pyqtSignal()
52
53 def __init__(self, parent=None):
54 """
55 Constructor
56
57 @param parent reference to the parent object (QObject)
58 """
59 super(BookmarksManager, self).__init__(parent)
60
61 self.__saveTimer = AutoSaver(self, self.save)
62 self.entryAdded.connect(self.__saveTimer.changeOccurred)
63 self.entryRemoved.connect(self.__saveTimer.changeOccurred)
64 self.entryChanged.connect(self.__saveTimer.changeOccurred)
65
66 self.__initialize()
67
68 def __initialize(self):
69 """
70 Private method to initialize some data.
71 """
72 self.__loaded = False
73 self.__bookmarkRootNode = None
74 self.__toolbar = None
75 self.__menu = None
76 self.__bookmarksModel = None
77 self.__commands = QUndoStack()
78
79 @classmethod
80 def getFileName(cls):
81 """
82 Class method to get the file name of the bookmark file.
83
84 @return name of the bookmark file (string)
85 """
86 return os.path.join(Utilities.getConfigDir(), "web_browser",
87 "bookmarks.xbel")
88
89 def close(self):
90 """
91 Public method to close the bookmark manager.
92 """
93 self.__saveTimer.saveIfNeccessary()
94
95 def undoRedoStack(self):
96 """
97 Public method to get a reference to the undo stack.
98
99 @return reference to the undo stack (QUndoStack)
100 """
101 return self.__commands
102
103 def changeExpanded(self):
104 """
105 Public method to handle a change of the expanded state.
106 """
107 self.__saveTimer.changeOccurred()
108
109 def reload(self):
110 """
111 Public method used to initiate a reloading of the bookmarks.
112 """
113 self.__initialize()
114 self.load()
115 self.bookmarksReloaded.emit()
116
117 def load(self):
118 """
119 Public method to load the bookmarks.
120
121 @exception RuntimeError raised to indicate an error loading the
122 bookmarks
123 """
124 if self.__loaded:
125 return
126
127 self.__loaded = True
128
129 bookmarkFile = self.getFileName()
130 if not QFile.exists(bookmarkFile):
131 from . import DefaultBookmarks_rc # __IGNORE_WARNING__
132 bookmarkFile = QFile(":/DefaultBookmarks.xbel")
133 bookmarkFile.open(QIODevice.ReadOnly)
134
135 from .XbelReader import XbelReader
136 reader = XbelReader()
137 self.__bookmarkRootNode = reader.read(bookmarkFile)
138 if reader.error() != QXmlStreamReader.NoError:
139 E5MessageBox.warning(
140 None,
141 self.tr("Loading Bookmarks"),
142 self.tr(
143 """Error when loading bookmarks on line {0},"""
144 """ column {1}:\n {2}""")
145 .format(reader.lineNumber(),
146 reader.columnNumber(),
147 reader.errorString()))
148
149 others = []
150 for index in range(
151 len(self.__bookmarkRootNode.children()) - 1, -1, -1):
152 node = self.__bookmarkRootNode.children()[index]
153 if node.type() == BookmarkNode.Folder:
154 if (node.title == self.tr("Toolbar Bookmarks") or
155 node.title == BOOKMARKBAR) and \
156 self.__toolbar is None:
157 node.title = self.tr(BOOKMARKBAR)
158 self.__toolbar = node
159
160 if (node.title == self.tr("Menu") or
161 node.title == BOOKMARKMENU) and \
162 self.__menu is None:
163 node.title = self.tr(BOOKMARKMENU)
164 self.__menu = node
165 else:
166 others.append(node)
167 self.__bookmarkRootNode.remove(node)
168
169 if len(self.__bookmarkRootNode.children()) > 0:
170 raise RuntimeError("Error loading bookmarks.")
171
172 if self.__toolbar is None:
173 self.__toolbar = BookmarkNode(BookmarkNode.Folder,
174 self.__bookmarkRootNode)
175 self.__toolbar.title = self.tr(BOOKMARKBAR)
176 else:
177 self.__bookmarkRootNode.add(self.__toolbar)
178
179 if self.__menu is None:
180 self.__menu = BookmarkNode(BookmarkNode.Folder,
181 self.__bookmarkRootNode)
182 self.__menu.title = self.tr(BOOKMARKMENU)
183 else:
184 self.__bookmarkRootNode.add(self.__menu)
185
186 for node in others:
187 self.__menu.add(node)
188
189 def save(self):
190 """
191 Public method to save the bookmarks.
192 """
193 if not self.__loaded:
194 return
195
196 from .XbelWriter import XbelWriter
197 writer = XbelWriter()
198 bookmarkFile = self.getFileName()
199
200 # save root folder titles in English (i.e. not localized)
201 self.__menu.title = BOOKMARKMENU
202 self.__toolbar.title = BOOKMARKBAR
203 if not writer.write(bookmarkFile, self.__bookmarkRootNode):
204 E5MessageBox.warning(
205 None,
206 self.tr("Saving Bookmarks"),
207 self.tr("""Error saving bookmarks to <b>{0}</b>.""")
208 .format(bookmarkFile))
209
210 # restore localized titles
211 self.__menu.title = self.tr(BOOKMARKMENU)
212 self.__toolbar.title = self.tr(BOOKMARKBAR)
213
214 self.bookmarksSaved.emit()
215
216 def addBookmark(self, parent, node, row=-1):
217 """
218 Public method to add a bookmark.
219
220 @param parent reference to the node to add to (BookmarkNode)
221 @param node reference to the node to add (BookmarkNode)
222 @param row row number (integer)
223 """
224 if not self.__loaded:
225 return
226
227 self.setTimestamp(node, BookmarkNode.TsAdded,
228 QDateTime.currentDateTime())
229
230 command = InsertBookmarksCommand(self, parent, node, row)
231 self.__commands.push(command)
232
233 def removeBookmark(self, node):
234 """
235 Public method to remove a bookmark.
236
237 @param node reference to the node to be removed (BookmarkNode)
238 """
239 if not self.__loaded:
240 return
241
242 parent = node.parent()
243 row = parent.children().index(node)
244 command = RemoveBookmarksCommand(self, parent, row)
245 self.__commands.push(command)
246
247 def setTitle(self, node, newTitle):
248 """
249 Public method to set the title of a bookmark.
250
251 @param node reference to the node to be changed (BookmarkNode)
252 @param newTitle title to be set (string)
253 """
254 if not self.__loaded:
255 return
256
257 command = ChangeBookmarkCommand(self, node, newTitle, True)
258 self.__commands.push(command)
259
260 def setUrl(self, node, newUrl):
261 """
262 Public method to set the URL of a bookmark.
263
264 @param node reference to the node to be changed (BookmarkNode)
265 @param newUrl URL to be set (string)
266 """
267 if not self.__loaded:
268 return
269
270 command = ChangeBookmarkCommand(self, node, newUrl, False)
271 self.__commands.push(command)
272
273 def setNodeChanged(self, node):
274 """
275 Public method to signal changes of bookmarks other than title, URL
276 or timestamp.
277
278 @param node reference to the bookmark (BookmarkNode)
279 """
280 self.__saveTimer.changeOccurred()
281
282 def setTimestamp(self, node, timestampType, timestamp):
283 """
284 Public method to set the URL of a bookmark.
285
286 @param node reference to the node to be changed (BookmarkNode)
287 @param timestampType type of the timestamp to set
288 (BookmarkNode.TsAdded, BookmarkNode.TsModified,
289 BookmarkNode.TsVisited)
290 @param timestamp timestamp to set (QDateTime)
291 """
292 if not self.__loaded:
293 return
294
295 assert timestampType in [BookmarkNode.TsAdded,
296 BookmarkNode.TsModified,
297 BookmarkNode.TsVisited]
298
299 if timestampType == BookmarkNode.TsAdded:
300 node.added = timestamp
301 elif timestampType == BookmarkNode.TsModified:
302 node.modified = timestamp
303 elif timestampType == BookmarkNode.TsVisited:
304 node.visited = timestamp
305 self.__saveTimer.changeOccurred()
306
307 def incVisitCount(self, node):
308 """
309 Public method to increment the visit count of a bookmark.
310
311 @param node reference to the node to be changed (BookmarkNode)
312 """
313 if not self.__loaded:
314 return
315
316 if node:
317 node.visitCount += 1
318 self.__saveTimer.changeOccurred()
319
320 def setVisitCount(self, node, count):
321 """
322 Public method to set the visit count of a bookmark.
323
324 @param node reference to the node to be changed (BookmarkNode)
325 @param count visit count to be set (int or str)
326 """
327 try:
328 node.visitCount = int(count)
329 self.__saveTimer.changeOccurred()
330 except ValueError:
331 # ignore invalid values
332 pass
333
334 def bookmarks(self):
335 """
336 Public method to get a reference to the root bookmark node.
337
338 @return reference to the root bookmark node (BookmarkNode)
339 """
340 if not self.__loaded:
341 self.load()
342
343 return self.__bookmarkRootNode
344
345 def menu(self):
346 """
347 Public method to get a reference to the bookmarks menu node.
348
349 @return reference to the bookmarks menu node (BookmarkNode)
350 """
351 if not self.__loaded:
352 self.load()
353
354 return self.__menu
355
356 def toolbar(self):
357 """
358 Public method to get a reference to the bookmarks toolbar node.
359
360 @return reference to the bookmarks toolbar node (BookmarkNode)
361 """
362 if not self.__loaded:
363 self.load()
364
365 return self.__toolbar
366
367 def bookmarksModel(self):
368 """
369 Public method to get a reference to the bookmarks model.
370
371 @return reference to the bookmarks model (BookmarksModel)
372 """
373 if self.__bookmarksModel is None:
374 from .BookmarksModel import BookmarksModel
375 self.__bookmarksModel = BookmarksModel(self, self)
376 return self.__bookmarksModel
377
378 def importBookmarks(self):
379 """
380 Public method to import bookmarks.
381 """
382 from .BookmarksImportDialog import BookmarksImportDialog
383 dlg = BookmarksImportDialog()
384 if dlg.exec_() == QDialog.Accepted:
385 importRootNode = dlg.getImportedBookmarks()
386 if importRootNode is not None:
387 self.addBookmark(self.menu(), importRootNode)
388
389 def exportBookmarks(self):
390 """
391 Public method to export the bookmarks.
392 """
393 fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
394 None,
395 self.tr("Export Bookmarks"),
396 "eric6_bookmarks.xbel",
397 self.tr("XBEL bookmarks (*.xbel);;"
398 "XBEL bookmarks (*.xml);;"
399 "HTML Bookmarks (*.html)"))
400 if not fileName:
401 return
402
403 ext = QFileInfo(fileName).suffix()
404 if not ext:
405 ex = selectedFilter.split("(*")[1].split(")")[0]
406 if ex:
407 fileName += ex
408
409 ext = QFileInfo(fileName).suffix()
410 if ext == "html":
411 from .NsHtmlWriter import NsHtmlWriter
412 writer = NsHtmlWriter()
413 else:
414 from .XbelWriter import XbelWriter
415 writer = XbelWriter()
416 if not writer.write(fileName, self.__bookmarkRootNode):
417 E5MessageBox.critical(
418 None,
419 self.tr("Exporting Bookmarks"),
420 self.tr("""Error exporting bookmarks to <b>{0}</b>.""")
421 .format(fileName))
422
423 def faviconChanged(self, url):
424 """
425 Public slot to update the icon image for an URL.
426
427 @param url URL of the icon to update (QUrl or string)
428 """
429 if isinstance(url, QUrl):
430 url = url.toString()
431 nodes = self.bookmarksForUrl(url)
432 for node in nodes:
433 self.bookmarksModel().entryChanged(node)
434
435 def bookmarkForUrl(self, url, start=StartRoot):
436 """
437 Public method to get a bookmark node for a given URL.
438
439 @param url URL of the bookmark to search for (QUrl or string)
440 @keyparam start indicator for the start of the search
441 (StartRoot, StartMenu, StartToolBar)
442 @return bookmark node for the given url (BookmarkNode)
443 """
444 if start == StartMenu:
445 startNode = self.__menu
446 elif start == StartToolBar:
447 startNode = self.__toolbar
448 else:
449 startNode = self.__bookmarkRootNode
450 if startNode is None:
451 return None
452
453 if isinstance(url, QUrl):
454 url = url.toString()
455
456 return self.__searchBookmark(url, startNode)
457
458 def __searchBookmark(self, url, startNode):
459 """
460 Private method get a bookmark node for a given URL.
461
462 @param url URL of the bookmark to search for (string)
463 @param startNode reference to the node to start searching
464 (BookmarkNode)
465 @return bookmark node for the given url (BookmarkNode)
466 """
467 bm = None
468 for node in startNode.children():
469 if node.type() == BookmarkNode.Folder:
470 bm = self.__searchBookmark(url, node)
471 elif node.type() == BookmarkNode.Bookmark:
472 if node.url == url:
473 bm = node
474 if bm is not None:
475 return bm
476 return None
477
478 def bookmarksForUrl(self, url, start=StartRoot):
479 """
480 Public method to get a list of bookmark nodes for a given URL.
481
482 @param url URL of the bookmarks to search for (QUrl or string)
483 @keyparam start indicator for the start of the search
484 (StartRoot, StartMenu, StartToolBar)
485 @return list of bookmark nodes for the given url (list of BookmarkNode)
486 """
487 if start == StartMenu:
488 startNode = self.__menu
489 elif start == StartToolBar:
490 startNode = self.__toolbar
491 else:
492 startNode = self.__bookmarkRootNode
493 if startNode is None:
494 return []
495
496 if isinstance(url, QUrl):
497 url = url.toString()
498
499 return self.__searchBookmarks(url, startNode)
500
501 def __searchBookmarks(self, url, startNode):
502 """
503 Private method get a list of bookmark nodes for a given URL.
504
505 @param url URL of the bookmarks to search for (string)
506 @param startNode reference to the node to start searching
507 (BookmarkNode)
508 @return list of bookmark nodes for the given url (list of BookmarkNode)
509 """
510 bm = []
511 for node in startNode.children():
512 if node.type() == BookmarkNode.Folder:
513 bm.extend(self.__searchBookmarks(url, node))
514 elif node.type() == BookmarkNode.Bookmark:
515 if node.url == url:
516 bm.append(node)
517 return bm
518
519
520 class RemoveBookmarksCommand(QUndoCommand):
521 """
522 Class implementing the Remove undo command.
523 """
524 def __init__(self, bookmarksManager, parent, row):
525 """
526 Constructor
527
528 @param bookmarksManager reference to the bookmarks manager
529 (BookmarksManager)
530 @param parent reference to the parent node (BookmarkNode)
531 @param row row number of bookmark (integer)
532 """
533 super(RemoveBookmarksCommand, self).__init__(
534 QCoreApplication.translate("BookmarksManager", "Remove Bookmark"))
535
536 self._row = row
537 self._bookmarksManager = bookmarksManager
538 try:
539 self._node = parent.children()[row]
540 except IndexError:
541 self._node = BookmarkNode()
542 self._parent = parent
543
544 def undo(self):
545 """
546 Public slot to perform the undo action.
547 """
548 self._parent.add(self._node, self._row)
549 self._bookmarksManager.entryAdded.emit(self._node)
550
551 def redo(self):
552 """
553 Public slot to perform the redo action.
554 """
555 self._parent.remove(self._node)
556 self._bookmarksManager.entryRemoved.emit(
557 self._parent, self._row, self._node)
558
559
560 class InsertBookmarksCommand(RemoveBookmarksCommand):
561 """
562 Class implementing the Insert undo command.
563 """
564 def __init__(self, bookmarksManager, parent, node, row):
565 """
566 Constructor
567
568 @param bookmarksManager reference to the bookmarks manager
569 (BookmarksManager)
570 @param parent reference to the parent node (BookmarkNode)
571 @param node reference to the node to be inserted (BookmarkNode)
572 @param row row number of bookmark (integer)
573 """
574 RemoveBookmarksCommand.__init__(self, bookmarksManager, parent, row)
575 self.setText(QCoreApplication.translate(
576 "BookmarksManager", "Insert Bookmark"))
577 self._node = node
578
579 def undo(self):
580 """
581 Public slot to perform the undo action.
582 """
583 RemoveBookmarksCommand.redo(self)
584
585 def redo(self):
586 """
587 Public slot to perform the redo action.
588 """
589 RemoveBookmarksCommand.undo(self)
590
591
592 class ChangeBookmarkCommand(QUndoCommand):
593 """
594 Class implementing the Insert undo command.
595 """
596 def __init__(self, bookmarksManager, node, newValue, title):
597 """
598 Constructor
599
600 @param bookmarksManager reference to the bookmarks manager
601 (BookmarksManager)
602 @param node reference to the node to be changed (BookmarkNode)
603 @param newValue new value to be set (string)
604 @param title flag indicating a change of the title (True) or
605 the URL (False) (boolean)
606 """
607 super(ChangeBookmarkCommand, self).__init__()
608
609 self._bookmarksManager = bookmarksManager
610 self._title = title
611 self._newValue = newValue
612 self._node = node
613
614 if self._title:
615 self._oldValue = self._node.title
616 self.setText(QCoreApplication.translate(
617 "BookmarksManager", "Name Change"))
618 else:
619 self._oldValue = self._node.url
620 self.setText(QCoreApplication.translate(
621 "BookmarksManager", "Address Change"))
622
623 def undo(self):
624 """
625 Public slot to perform the undo action.
626 """
627 if self._title:
628 self._node.title = self._oldValue
629 else:
630 self._node.url = self._oldValue
631 self._bookmarksManager.entryChanged.emit(self._node)
632
633 def redo(self):
634 """
635 Public slot to perform the redo action.
636 """
637 if self._title:
638 self._node.title = self._newValue
639 else:
640 self._node.url = self._newValue
641 self._bookmarksManager.entryChanged.emit(self._node)

eric ide

mercurial