eric6/Helpviewer/Bookmarks/BookmarksManager.py

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

eric ide

mercurial