Helpviewer/Bookmarks/BookmarksManager.py

changeset 0
de9c2efb9d02
child 7
c679fb30c8f3
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
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, '&amp;') + "\">\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)

eric ide

mercurial