WebBrowser/Bookmarks/BookmarksManager.py

branch
QtWebEngine
changeset 4732
5ac4fc1dfc20
parent 4631
5c1a96925da4
child 4745
285bfd224a1b
equal deleted inserted replaced
4731:67d861d9e492 4732:5ac4fc1dfc20
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2016 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(), "web_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 # TODO: Bookmarks: remove this obsolete method
400 def __convertFromOldBookmarks(self):
401 """
402 Private method to convert the old bookmarks into the new ones.
403 """
404 bmNames = Preferences.Prefs.settings.value('Bookmarks/Names')
405 bmFiles = Preferences.Prefs.settings.value('Bookmarks/Files')
406
407 if bmNames is not None and bmFiles is not None:
408 if len(bmNames) == len(bmFiles):
409 convertedRootNode = BookmarkNode(BookmarkNode.Folder)
410 convertedRootNode.title = self.tr("Converted {0}")\
411 .format(QDate.currentDate().toString(
412 Qt.SystemLocaleShortDate))
413 for i in range(len(bmNames)):
414 node = BookmarkNode(BookmarkNode.Bookmark,
415 convertedRootNode)
416 node.title = bmNames[i]
417 url = QUrl(bmFiles[i])
418 if not url.scheme():
419 url.setScheme("file")
420 node.url = url.toString()
421 self.addBookmark(self.menu(), convertedRootNode)
422
423 Preferences.Prefs.settings.remove('Bookmarks')
424
425 def iconChanged(self, url):
426 """
427 Public slot to update the icon image for an URL.
428
429 @param url URL of the icon to update (QUrl or string)
430 """
431 if isinstance(url, QUrl):
432 url = url.toString()
433 nodes = self.bookmarksForUrl(url)
434 for node in nodes:
435 self.bookmarksModel().entryChanged(node)
436
437 def bookmarkForUrl(self, url, start=StartRoot):
438 """
439 Public method to get a bookmark node for a given URL.
440
441 @param url URL of the bookmark to search for (QUrl or string)
442 @keyparam start indicator for the start of the search
443 (StartRoot, StartMenu, StartToolBar)
444 @return bookmark node for the given url (BookmarkNode)
445 """
446 if start == StartMenu:
447 startNode = self.__menu
448 elif start == StartToolBar:
449 startNode = self.__toolbar
450 else:
451 startNode = self.__bookmarkRootNode
452 if startNode is None:
453 return None
454
455 if isinstance(url, QUrl):
456 url = url.toString()
457
458 return self.__searchBookmark(url, startNode)
459
460 def __searchBookmark(self, url, startNode):
461 """
462 Private method get a bookmark node for a given URL.
463
464 @param url URL of the bookmark to search for (string)
465 @param startNode reference to the node to start searching
466 (BookmarkNode)
467 @return bookmark node for the given url (BookmarkNode)
468 """
469 bm = None
470 for node in startNode.children():
471 if node.type() == BookmarkNode.Folder:
472 bm = self.__searchBookmark(url, node)
473 elif node.type() == BookmarkNode.Bookmark:
474 if node.url == url:
475 bm = node
476 if bm is not None:
477 return bm
478 return None
479
480 def bookmarksForUrl(self, url, start=StartRoot):
481 """
482 Public method to get a list of bookmark nodes for a given URL.
483
484 @param url URL of the bookmarks to search for (QUrl or string)
485 @keyparam start indicator for the start of the search
486 (StartRoot, StartMenu, StartToolBar)
487 @return list of bookmark nodes for the given url (list of BookmarkNode)
488 """
489 if start == StartMenu:
490 startNode = self.__menu
491 elif start == StartToolBar:
492 startNode = self.__toolbar
493 else:
494 startNode = self.__bookmarkRootNode
495 if startNode is None:
496 return []
497
498 if isinstance(url, QUrl):
499 url = url.toString()
500
501 return self.__searchBookmarks(url, startNode)
502
503 def __searchBookmarks(self, url, startNode):
504 """
505 Private method get a list of bookmark nodes for a given URL.
506
507 @param url URL of the bookmarks to search for (string)
508 @param startNode reference to the node to start searching
509 (BookmarkNode)
510 @return list of bookmark nodes for the given url (list of BookmarkNode)
511 """
512 bm = []
513 for node in startNode.children():
514 if node.type() == BookmarkNode.Folder:
515 bm.extend(self.__searchBookmarks(url, node))
516 elif node.type() == BookmarkNode.Bookmark:
517 if node.url == url:
518 bm.append(node)
519 return bm
520
521
522 class RemoveBookmarksCommand(QUndoCommand):
523 """
524 Class implementing the Remove undo command.
525 """
526 def __init__(self, bookmarksManager, parent, row):
527 """
528 Constructor
529
530 @param bookmarksManager reference to the bookmarks manager
531 (BookmarksManager)
532 @param parent reference to the parent node (BookmarkNode)
533 @param row row number of bookmark (integer)
534 """
535 super(RemoveBookmarksCommand, self).__init__(
536 QCoreApplication.translate("BookmarksManager", "Remove Bookmark"))
537
538 self._row = row
539 self._bookmarksManager = bookmarksManager
540 try:
541 self._node = parent.children()[row]
542 except IndexError:
543 self._node = BookmarkNode()
544 self._parent = parent
545
546 def undo(self):
547 """
548 Public slot to perform the undo action.
549 """
550 self._parent.add(self._node, self._row)
551 self._bookmarksManager.entryAdded.emit(self._node)
552
553 def redo(self):
554 """
555 Public slot to perform the redo action.
556 """
557 self._parent.remove(self._node)
558 self._bookmarksManager.entryRemoved.emit(
559 self._parent, self._row, self._node)
560
561
562 class InsertBookmarksCommand(RemoveBookmarksCommand):
563 """
564 Class implementing the Insert undo command.
565 """
566 def __init__(self, bookmarksManager, parent, node, row):
567 """
568 Constructor
569
570 @param bookmarksManager reference to the bookmarks manager
571 (BookmarksManager)
572 @param parent reference to the parent node (BookmarkNode)
573 @param node reference to the node to be inserted (BookmarkNode)
574 @param row row number of bookmark (integer)
575 """
576 RemoveBookmarksCommand.__init__(self, bookmarksManager, parent, row)
577 self.setText(QCoreApplication.translate(
578 "BookmarksManager", "Insert Bookmark"))
579 self._node = node
580
581 def undo(self):
582 """
583 Public slot to perform the undo action.
584 """
585 RemoveBookmarksCommand.redo(self)
586
587 def redo(self):
588 """
589 Public slot to perform the redo action.
590 """
591 RemoveBookmarksCommand.undo(self)
592
593
594 class ChangeBookmarkCommand(QUndoCommand):
595 """
596 Class implementing the Insert undo command.
597 """
598 def __init__(self, bookmarksManager, node, newValue, title):
599 """
600 Constructor
601
602 @param bookmarksManager reference to the bookmarks manager
603 (BookmarksManager)
604 @param node reference to the node to be changed (BookmarkNode)
605 @param newValue new value to be set (string)
606 @param title flag indicating a change of the title (True) or
607 the URL (False) (boolean)
608 """
609 super(ChangeBookmarkCommand, self).__init__()
610
611 self._bookmarksManager = bookmarksManager
612 self._title = title
613 self._newValue = newValue
614 self._node = node
615
616 if self._title:
617 self._oldValue = self._node.title
618 self.setText(QCoreApplication.translate(
619 "BookmarksManager", "Name Change"))
620 else:
621 self._oldValue = self._node.url
622 self.setText(QCoreApplication.translate(
623 "BookmarksManager", "Address Change"))
624
625 def undo(self):
626 """
627 Public slot to perform the undo action.
628 """
629 if self._title:
630 self._node.title = self._oldValue
631 else:
632 self._node.url = self._oldValue
633 self._bookmarksManager.entryChanged.emit(self._node)
634
635 def redo(self):
636 """
637 Public slot to perform the redo action.
638 """
639 if self._title:
640 self._node.title = self._newValue
641 else:
642 self._node.url = self._newValue
643 self._bookmarksManager.entryChanged.emit(self._node)

eric ide

mercurial