|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the bookmarks manager. |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 from PyQt4.QtCore import * |
|
13 from PyQt4.QtGui import * |
|
14 from PyQt4.QtWebKit import QWebPage |
|
15 |
|
16 from BookmarkNode import BookmarkNode |
|
17 from BookmarksModel import BookmarksModel |
|
18 from DefaultBookmarks import DefaultBookmarks |
|
19 from XbelReader import XbelReader |
|
20 from XbelWriter import XbelWriter |
|
21 |
|
22 from Utilities.AutoSaver import AutoSaver |
|
23 import Utilities |
|
24 import Preferences |
|
25 |
|
26 BOOKMARKBAR = QT_TRANSLATE_NOOP("BookmarksManager", "Bookmarks Bar") |
|
27 BOOKMARKMENU = QT_TRANSLATE_NOOP("BookmarksManager", "Bookmarks Menu") |
|
28 |
|
29 ########################################################################################## |
|
30 |
|
31 extract_js = r""" |
|
32 function walk() { |
|
33 var parent = arguments[0]; |
|
34 var indent = arguments[1]; |
|
35 |
|
36 var result = ""; |
|
37 var children = parent.childNodes; |
|
38 var folderName = ""; |
|
39 var folded = ""; |
|
40 for (var i = 0; i < children.length; i++) { |
|
41 var object = children.item(i); |
|
42 if (object.nodeName == "HR") { |
|
43 result += indent + "<separator/>\n"; |
|
44 } |
|
45 if (object.nodeName == "H3") { |
|
46 folderName = object.innerHTML; |
|
47 folded = object.folded; |
|
48 if (object.folded == undefined) |
|
49 folded = "false"; |
|
50 else |
|
51 folded = "true"; |
|
52 } |
|
53 if (object.nodeName == "A") { |
|
54 result += indent + "<bookmark href=\"" + encodeURI(object.href).replace(/&/g, '&') + "\">\n"; |
|
55 result += indent + indent + "<title>" + object.innerHTML + "</title>\n"; |
|
56 result += indent + "</bookmark>\n"; |
|
57 } |
|
58 |
|
59 var currentIndent = indent; |
|
60 if (object.nodeName == "DL" && folderName != "") { |
|
61 result += indent + "<folder folded=\"" + folded + "\">\n"; |
|
62 indent += " "; |
|
63 result += indent + "<title>" + folderName + "</title>\n"; |
|
64 } |
|
65 result += walk(object, indent); |
|
66 if (object.nodeName == "DL" && folderName != "") { |
|
67 result += currentIndent + "</folder>\n"; |
|
68 } |
|
69 } |
|
70 return result; |
|
71 } |
|
72 |
|
73 var xbel = walk(document, " "); |
|
74 |
|
75 if (xbel != "") { |
|
76 xbel = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE xbel>\n<xbel version=\"1.0\">\n" + xbel + "</xbel>\n"; |
|
77 } |
|
78 |
|
79 xbel; |
|
80 """ |
|
81 |
|
82 ########################################################################################## |
|
83 |
|
84 class BookmarksManager(QObject): |
|
85 """ |
|
86 Class implementing the bookmarks manager. |
|
87 |
|
88 @signal entryAdded emitted after a bookmark node has been added |
|
89 @signal entryRemoved emitted after a bookmark |
|
90 node has been removed |
|
91 @signal entryChanged emitted after a bookmark node has been changed |
|
92 """ |
|
93 def __init__(self, parent = None): |
|
94 """ |
|
95 Constructor |
|
96 |
|
97 @param parent reference to the parent object (QObject) |
|
98 """ |
|
99 QObject.__init__(self, parent) |
|
100 |
|
101 self.__loaded = False |
|
102 self.__saveTimer = AutoSaver(self, self.save) |
|
103 self.__bookmarkRootNode = None |
|
104 self.__toolbar = None |
|
105 self.__menu = None |
|
106 self.__bookmarksModel = None |
|
107 self.__commands = QUndoStack() |
|
108 |
|
109 self.connect(self, SIGNAL("entryAdded"), |
|
110 self.__saveTimer.changeOccurred) |
|
111 self.connect(self, SIGNAL("entryRemoved"), |
|
112 self.__saveTimer.changeOccurred) |
|
113 self.connect(self, SIGNAL("entryChanged"), |
|
114 self.__saveTimer.changeOccurred) |
|
115 |
|
116 def close(self): |
|
117 """ |
|
118 Public method to close the bookmark manager. |
|
119 """ |
|
120 self.__saveTimer.saveIfNeccessary() |
|
121 |
|
122 def undoRedoStack(self): |
|
123 """ |
|
124 Public method to get a reference to the undo stack. |
|
125 |
|
126 @return reference to the undo stack (QUndoStack) |
|
127 """ |
|
128 return self.__commands |
|
129 |
|
130 def changeExpanded(self): |
|
131 """ |
|
132 Public method to handle a change of the expanded state. |
|
133 """ |
|
134 self.__saveTimer.changeOccurred() |
|
135 |
|
136 def load(self): |
|
137 """ |
|
138 Public method to load the bookmarks. |
|
139 """ |
|
140 if self.__loaded: |
|
141 return |
|
142 |
|
143 self.__loaded = True |
|
144 |
|
145 bookmarkFile = os.path.join(Utilities.getConfigDir(), "browser", "bookmarks.xbel") |
|
146 if not QFile.exists(bookmarkFile): |
|
147 ba = QByteArray(DefaultBookmarks) |
|
148 bookmarkFile = QBuffer(ba) |
|
149 bookmarkFile.open(QIODevice.ReadOnly) |
|
150 |
|
151 reader = XbelReader() |
|
152 self.__bookmarkRootNode = reader.read(bookmarkFile) |
|
153 if reader.error() != QXmlStreamReader.NoError: |
|
154 QMessageBox.warning(None, |
|
155 self.trUtf8("Loading Bookmarks"), |
|
156 self.trUtf8("""Error when loading bookmarks on line {0}, column {1}:\n""" |
|
157 """{3}""")\ |
|
158 .format(reader.lineNumber(), |
|
159 reader.columnNumber(), |
|
160 reader.errorString())) |
|
161 |
|
162 others = [] |
|
163 for index in range(len(self.__bookmarkRootNode.children()) - 1, -1, -1): |
|
164 node = self.__bookmarkRootNode.children()[index] |
|
165 if node.type() == BookmarkNode.Folder: |
|
166 if (node.title == self.trUtf8("Toolbar Bookmarks") or \ |
|
167 node.title == BOOKMARKBAR) and \ |
|
168 self.__toolbar is None: |
|
169 node.title = self.trUtf8(BOOKMARKBAR) |
|
170 self.__toolbar = node |
|
171 |
|
172 if (node.title == self.trUtf8("Menu") or \ |
|
173 node.title == BOOKMARKMENU) and \ |
|
174 self.__menu is None: |
|
175 node.title = self.trUtf8(BOOKMARKMENU) |
|
176 self.__menu = node |
|
177 else: |
|
178 others.append(node) |
|
179 self.__bookmarkRootNode.remove(node) |
|
180 |
|
181 if len(self.__bookmarkRootNode.children()) > 0: |
|
182 raise RuntimeError("Error loading bookmarks.") |
|
183 |
|
184 if self.__toolbar is None: |
|
185 self.__toolbar = BookmarkNode(BookmarkNode.Folder, self.__bookmarkRootNode) |
|
186 self.__toolbar.title = self.trUtf8(BOOKMARKBAR) |
|
187 else: |
|
188 self.__bookmarkRootNode.add(self.__toolbar) |
|
189 |
|
190 if self.__menu is None: |
|
191 self.__menu = BookmarkNode(BookmarkNode.Folder, self.__bookmarkRootNode) |
|
192 self.__menu.title = self.trUtf8(BOOKMARKMENU) |
|
193 else: |
|
194 self.__bookmarkRootNode.add(self.__menu) |
|
195 |
|
196 for node in others: |
|
197 self.__menu.add(node) |
|
198 |
|
199 self.__convertFromOldBookmarks() |
|
200 |
|
201 def save(self): |
|
202 """ |
|
203 Public method to save the bookmarks. |
|
204 """ |
|
205 if not self.__loaded: |
|
206 return |
|
207 |
|
208 writer = XbelWriter() |
|
209 bookmarkFile = os.path.join(Utilities.getConfigDir(), "browser", "bookmarks.xbel") |
|
210 |
|
211 # save root folder titles in English (i.e. not localized) |
|
212 self.__menu.title = BOOKMARKMENU |
|
213 self.__toolbar.title = BOOKMARKBAR |
|
214 if not writer.write(bookmarkFile, self.__bookmarkRootNode): |
|
215 QMessageBox.warning(None, |
|
216 self.trUtf8("Saving Bookmarks"), |
|
217 self.trUtf8("""Error saving bookmarks to <b>{0}</b>.""")\ |
|
218 .format(bookmarkFile)) |
|
219 |
|
220 # restore localized titles |
|
221 self.__menu.title = self.trUtf8(BOOKMARKMENU) |
|
222 self.__toolbar.title = self.trUtf8(BOOKMARKBAR) |
|
223 |
|
224 def addBookmark(self, parent, node, row = -1): |
|
225 """ |
|
226 Public method to add a bookmark. |
|
227 |
|
228 @param parent reference to the node to add to (BookmarkNode) |
|
229 @param node reference to the node to add (BookmarkNode) |
|
230 @param row row number (integer) |
|
231 """ |
|
232 if not self.__loaded: |
|
233 return |
|
234 |
|
235 command = InsertBookmarksCommand(self, parent, node, row) |
|
236 self.__commands.push(command) |
|
237 |
|
238 def removeBookmark(self, node): |
|
239 """ |
|
240 Public method to remove a bookmark. |
|
241 |
|
242 @param node reference to the node to be removed (BookmarkNode) |
|
243 """ |
|
244 if not self.__loaded: |
|
245 return |
|
246 |
|
247 parent = node.parent() |
|
248 row = parent.children().index(node) |
|
249 command = RemoveBookmarksCommand(self, parent, row) |
|
250 self.__commands.push(command) |
|
251 |
|
252 def setTitle(self, node, newTitle): |
|
253 """ |
|
254 Public method to set the title of a bookmark. |
|
255 |
|
256 @param node reference to the node to be changed (BookmarkNode) |
|
257 @param newTitle title to be set (string) |
|
258 """ |
|
259 if not self.__loaded: |
|
260 return |
|
261 |
|
262 command = ChangeBookmarkCommand(self, node, newTitle, True) |
|
263 self.__commands.push(command) |
|
264 |
|
265 def setUrl(self, node, newUrl): |
|
266 """ |
|
267 Public method to set the URL of a bookmark. |
|
268 |
|
269 @param node reference to the node to be changed (BookmarkNode) |
|
270 @param newUrl URL to be set (string) |
|
271 """ |
|
272 if not self.__loaded: |
|
273 return |
|
274 |
|
275 command = ChangeBookmarkCommand(self, node, newUrl, False) |
|
276 self.__commands.push(command) |
|
277 |
|
278 def bookmarks(self): |
|
279 """ |
|
280 Public method to get a reference to the root bookmark node. |
|
281 |
|
282 @return reference to the root bookmark node (BookmarkNode) |
|
283 """ |
|
284 if not self.__loaded: |
|
285 self.load() |
|
286 |
|
287 return self.__bookmarkRootNode |
|
288 |
|
289 def menu(self): |
|
290 """ |
|
291 Public method to get a reference to the bookmarks menu node. |
|
292 |
|
293 @return reference to the bookmarks menu node (BookmarkNode) |
|
294 """ |
|
295 if not self.__loaded: |
|
296 self.load() |
|
297 |
|
298 return self.__menu |
|
299 |
|
300 def toolbar(self): |
|
301 """ |
|
302 Public method to get a reference to the bookmarks toolbar node. |
|
303 |
|
304 @return reference to the bookmarks toolbar node (BookmarkNode) |
|
305 """ |
|
306 if not self.__loaded: |
|
307 self.load() |
|
308 |
|
309 return self.__toolbar |
|
310 |
|
311 def bookmarksModel(self): |
|
312 """ |
|
313 Public method to get a reference to the bookmarks model. |
|
314 |
|
315 @return reference to the bookmarks model (BookmarksModel) |
|
316 """ |
|
317 if self.__bookmarksModel is None: |
|
318 self.__bookmarksModel = BookmarksModel(self, self) |
|
319 return self.__bookmarksModel |
|
320 |
|
321 def importBookmarks(self): |
|
322 """ |
|
323 Public method to import bookmarks. |
|
324 """ |
|
325 supportedFormats = [ |
|
326 self.trUtf8("XBEL bookmarks") + " (*.xbel *.xml)", |
|
327 self.trUtf8("HTML Netscape bookmarks") + " (*.html)" |
|
328 ] |
|
329 |
|
330 fileName = QFileDialog.getOpenFileName( |
|
331 None, |
|
332 self.trUtf8("Import Bookmarks"), |
|
333 "", |
|
334 ";;".join(supportedFormats)) |
|
335 if not fileName: |
|
336 return |
|
337 |
|
338 reader = XbelReader() |
|
339 importRootNode = None |
|
340 if fileName.endswith(".html"): |
|
341 inFile = QFile(fileName) |
|
342 inFile.open(QIODevice.ReadOnly) |
|
343 if inFile.openMode == QIODevice.NotOpen: |
|
344 QMessageBox.warning(None, |
|
345 self.trUtf8("Import Bookmarks"), |
|
346 self.trUtf8("""Error opening bookmarks file <b>{0}</b>.""")\ |
|
347 .format(fileName)) |
|
348 return |
|
349 |
|
350 webpage = QWebPage() |
|
351 webpage.mainFrame().setHtml(unicode(inFile.readAll())) |
|
352 result = webpage.mainFrame().evaluateJavaScript(extract_js).toByteArray() |
|
353 buffer_ = QBuffer(result) |
|
354 buffer_.open(QIODevice.ReadOnly) |
|
355 importRootNode = reader.read(buffer_) |
|
356 else: |
|
357 importRootNode = reader.read(fileName) |
|
358 |
|
359 if reader.error() != QXmlStreamReader.NoError: |
|
360 QMessageBox.warning(None, |
|
361 self.trUtf8("Import Bookmarks"), |
|
362 self.trUtf8("""Error when importing bookmarks on""" |
|
363 """ line {0}, column [1}:\n{2}""")\ |
|
364 .format(reader.lineNumber(), |
|
365 reader.columnNumber(), |
|
366 reader.errorString())) |
|
367 return |
|
368 |
|
369 importRootNode.setType(BookmarkNode.Folder) |
|
370 importRootNode.title = self.trUtf8("Imported {0}")\ |
|
371 .format(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) |
|
372 self.addBookmark(self.menu(), importRootNode) |
|
373 |
|
374 def exportBookmarks(self): |
|
375 """ |
|
376 Public method to export the bookmarks. |
|
377 """ |
|
378 fileName = QFileDialog.getSaveFileName( |
|
379 None, |
|
380 self.trUtf8("Export Bookmarks"), |
|
381 "eric4_bookmarks.xbel", |
|
382 self.trUtf8("XBEL bookmarks") + " (*.xbel, *.xml)") |
|
383 if not fileName: |
|
384 return |
|
385 |
|
386 writer = XbelWriter() |
|
387 if not writer.write(fileName, self.__bookmarkRootNode): |
|
388 QMessageBox.critical(None, |
|
389 self.trUtf8("Exporting Bookmarks"), |
|
390 self.trUtf8("""Error exporting bookmarks to <b>{0}</b>.""")\ |
|
391 .format(bookmarkFile)) |
|
392 |
|
393 def __convertFromOldBookmarks(self): |
|
394 """ |
|
395 Private method to convert the old bookmarks into the new ones. |
|
396 """ |
|
397 bmNames = Preferences.Prefs.settings.value('Bookmarks/Names') |
|
398 bmFiles = Preferences.Prefs.settings.value('Bookmarks/Files') |
|
399 |
|
400 if bmNames.isValid() and bmFiles.isValid(): |
|
401 bmNames = bmNames.toStringList() |
|
402 bmFiles = bmFiles.toStringList() |
|
403 if len(bmNames) == len(bmFiles): |
|
404 convertedRootNode = BookmarkNode(BookmarkNode.Folder) |
|
405 convertedRootNode.title = self.trUtf8("Converted {0}")\ |
|
406 .format(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) |
|
407 for i in range(len(bmNames)): |
|
408 node = BookmarkNode(BookmarkNode.Bookmark, convertedRootNode) |
|
409 node.title = bmNames[i] |
|
410 url = QUrl(bmFiles[i]) |
|
411 if not url.scheme(): |
|
412 url.setScheme("file") |
|
413 node.url = url.toString() |
|
414 self.addBookmark(self.menu(), convertedRootNode) |
|
415 |
|
416 Preferences.Prefs.settings.remove('Bookmarks') |
|
417 |
|
418 class RemoveBookmarksCommand(QUndoCommand): |
|
419 """ |
|
420 Class implementing the Remove undo command. |
|
421 """ |
|
422 def __init__(self, bookmarksManager, parent, row): |
|
423 """ |
|
424 Constructor |
|
425 |
|
426 @param bookmarksManager reference to the bookmarks manager (BookmarksManager) |
|
427 @param parent reference to the parent node (BookmarkNode) |
|
428 @param row row number of bookmark (integer) |
|
429 """ |
|
430 QUndoCommand.__init__(self, |
|
431 QApplication.translate("BookmarksManager", "Remove Bookmark")) |
|
432 |
|
433 self._row = row |
|
434 self._bookmarksManager = bookmarksManager |
|
435 try: |
|
436 self._node = parent.children()[row] |
|
437 except IndexError: |
|
438 self._node = BookmarkNode() |
|
439 self._parent = parent |
|
440 |
|
441 def undo(self): |
|
442 """ |
|
443 Public slot to perform the undo action. |
|
444 """ |
|
445 self._parent.add(self._node, self._row) |
|
446 self._bookmarksManager.emit(SIGNAL("entryAdded"), self._node) |
|
447 |
|
448 def redo(self): |
|
449 """ |
|
450 Public slot to perform the redo action. |
|
451 """ |
|
452 self._parent.remove(self._node) |
|
453 self._bookmarksManager.emit( |
|
454 SIGNAL("entryRemoved"), |
|
455 self._parent, self._row, self._node) |
|
456 |
|
457 class InsertBookmarksCommand(RemoveBookmarksCommand): |
|
458 """ |
|
459 Class implementing the Insert undo command. |
|
460 """ |
|
461 def __init__(self, bookmarksManager, parent, node, row): |
|
462 """ |
|
463 Constructor |
|
464 |
|
465 @param bookmarksManager reference to the bookmarks manager (BookmarksManager) |
|
466 @param parent reference to the parent node (BookmarkNode) |
|
467 @param node reference to the node to be inserted (BookmarkNode) |
|
468 @param row row number of bookmark (integer) |
|
469 """ |
|
470 RemoveBookmarksCommand.__init__(self, bookmarksManager, parent, row) |
|
471 self.setText(QApplication.translate("BookmarksManager", "Insert Bookmark")) |
|
472 self._node = node |
|
473 |
|
474 def undo(self): |
|
475 """ |
|
476 Public slot to perform the undo action. |
|
477 """ |
|
478 RemoveBookmarksCommand.redo(self) |
|
479 |
|
480 def redo(self): |
|
481 """ |
|
482 Public slot to perform the redo action. |
|
483 """ |
|
484 RemoveBookmarksCommand.undo(self) |
|
485 |
|
486 class ChangeBookmarkCommand(QUndoCommand): |
|
487 """ |
|
488 Class implementing the Insert undo command. |
|
489 """ |
|
490 def __init__(self, bookmarksManager, node, newValue, title): |
|
491 """ |
|
492 Constructor |
|
493 |
|
494 @param bookmarksManager reference to the bookmarks manager (BookmarksManager) |
|
495 @param node reference to the node to be changed (BookmarkNode) |
|
496 @param newValue new value to be set (string) |
|
497 @param title flag indicating a change of the title (True) or |
|
498 the URL (False) (boolean) |
|
499 """ |
|
500 QUndoCommand.__init__(self) |
|
501 |
|
502 self._bookmarksManager = bookmarksManager |
|
503 self._title = title |
|
504 self._newValue = newValue |
|
505 self._node = node |
|
506 |
|
507 if self._title: |
|
508 self._oldValue = self._node.title |
|
509 self.setText(QApplication.translate("BookmarksManager", "Name Change")) |
|
510 else: |
|
511 self._oldValue = self._node.url |
|
512 self.setText(QApplication.translate("BookmarksManager", "Address Change")) |
|
513 |
|
514 def undo(self): |
|
515 """ |
|
516 Public slot to perform the undo action. |
|
517 """ |
|
518 if self._title: |
|
519 self._node.title = self._oldValue |
|
520 else: |
|
521 self._node.url = self._oldValue |
|
522 self._bookmarksManager.emit(SIGNAL("entryChanged"), self._node) |
|
523 |
|
524 def redo(self): |
|
525 """ |
|
526 Public slot to perform the redo action. |
|
527 """ |
|
528 if self._title: |
|
529 self._node.title = self._newValue |
|
530 else: |
|
531 self._node.url = self._newValue |
|
532 self._bookmarksManager.emit(SIGNAL("entryChanged"), self._node) |