|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the bookmark model class. |
|
8 """ |
|
9 |
|
10 from PyQt4.QtCore import * |
|
11 |
|
12 from BookmarkNode import BookmarkNode |
|
13 from XbelWriter import XbelWriter |
|
14 from XbelReader import XbelReader |
|
15 |
|
16 import Helpviewer.HelpWindow |
|
17 |
|
18 import UI.PixmapCache |
|
19 |
|
20 class BookmarksModel(QAbstractItemModel): |
|
21 """ |
|
22 Class implementing the bookmark model. |
|
23 """ |
|
24 TypeRole = Qt.UserRole + 1 |
|
25 UrlRole = Qt.UserRole + 2 |
|
26 UrlStringRole = Qt.UserRole + 3 |
|
27 SeparatorRole = Qt.UserRole + 4 |
|
28 |
|
29 MIMETYPE = "application/bookmarks.xbel" |
|
30 |
|
31 def __init__(self, manager, parent = None): |
|
32 """ |
|
33 Constructor |
|
34 |
|
35 @param manager reference to the bookmark manager object (BookmarksManager) |
|
36 @param parent reference to the parent object (QObject) |
|
37 """ |
|
38 QAbstractItemModel.__init__(self, parent) |
|
39 |
|
40 self.__endMacro = False |
|
41 self.__bookmarksManager = manager |
|
42 |
|
43 self.connect(manager, |
|
44 SIGNAL("entryAdded"), |
|
45 self.entryAdded) |
|
46 self.connect(manager, |
|
47 SIGNAL("entryRemoved"), |
|
48 self.entryRemoved) |
|
49 self.connect(manager, |
|
50 SIGNAL("entryChanged"), |
|
51 self.entryChanged) |
|
52 |
|
53 self.__headers = [ |
|
54 self.trUtf8("Title"), |
|
55 self.trUtf8("Address"), |
|
56 ] |
|
57 |
|
58 def bookmarksManager(self): |
|
59 """ |
|
60 Public method to get a reference to the bookmarks manager. |
|
61 |
|
62 @return reference to the bookmarks manager object (BookmarksManager) |
|
63 """ |
|
64 return self.__bookmarksManager |
|
65 |
|
66 def nodeIndex(self, node): |
|
67 """ |
|
68 Public method to get a model index. |
|
69 |
|
70 @param node reference to the node to get the index for (BookmarkNode) |
|
71 @return model index (QModelIndex) |
|
72 """ |
|
73 parent = node.parent() |
|
74 if parent is None: |
|
75 return QModelIndex() |
|
76 return self.createIndex(parent.children().index(node), 0, node) |
|
77 |
|
78 def entryAdded(self, node): |
|
79 """ |
|
80 Public slot to add a bookmark node. |
|
81 |
|
82 @param node reference to the bookmark node to add (BookmarkNode) |
|
83 """ |
|
84 if node is None or node.parent() is None: |
|
85 return |
|
86 |
|
87 parent = node.parent() |
|
88 row = parent.children().index(node) |
|
89 # node was already added so remove before beginInsertRows is called |
|
90 parent.remove(node) |
|
91 self.beginInsertRows(self.nodeIndex(parent), row, row) |
|
92 parent.add(node, row) |
|
93 self.endInsertRows() |
|
94 |
|
95 def entryRemoved(self, parent, row, node): |
|
96 """ |
|
97 Public slot to remove a bookmark node. |
|
98 |
|
99 @param parent reference to the parent bookmark node (BookmarkNode) |
|
100 @param row row number of the node (integer) |
|
101 @param node reference to the bookmark node to remove (BookmarkNode) |
|
102 """ |
|
103 # node was already removed, re-add so beginRemoveRows works |
|
104 parent.add(node, row) |
|
105 self.beginRemoveRows(self.nodeIndex(parent), row, row) |
|
106 parent.remove(node) |
|
107 self.endRemoveRows() |
|
108 |
|
109 def entryChanged(self, node): |
|
110 """ |
|
111 Public method to change a node. |
|
112 |
|
113 @param node reference to the bookmark node to change (BookmarkNode) |
|
114 """ |
|
115 idx = self.nodeIndex(node) |
|
116 self.emit(SIGNAL("dataChanged(const QModelIndex&, const QModelIndex&)"), |
|
117 idx, idx) |
|
118 |
|
119 def removeRows(self, row, count, parent = QModelIndex()): |
|
120 """ |
|
121 Public method to remove bookmarks from the model. |
|
122 |
|
123 @param row row of the first bookmark to remove (integer) |
|
124 @param count number of bookmarks to remove (integer) |
|
125 @param index of the parent bookmark node (QModelIndex) |
|
126 @return flag indicating successful removal (boolean) |
|
127 """ |
|
128 if row < 0 or count <= 0 or row + count > self.rowCount(parent): |
|
129 return False |
|
130 |
|
131 bookmarkNode = self.node(parent) |
|
132 children = bookmarkNode.children()[row:(row + count)] |
|
133 for node in children: |
|
134 if node == self.__bookmarksManager.menu() or \ |
|
135 node == self.__bookmarksManager.toolbar(): |
|
136 continue |
|
137 self.__bookmarksManager.removeBookmark(node) |
|
138 |
|
139 if self.__endMacro: |
|
140 self.__bookmarksManager.undoRedoStack().endMacro() |
|
141 self.__endMacro = False |
|
142 |
|
143 return True |
|
144 |
|
145 def headerData(self, section, orientation, role = Qt.DisplayRole): |
|
146 """ |
|
147 Public method to get the header data. |
|
148 |
|
149 @param section section number (integer) |
|
150 @param orientation header orientation (Qt.Orientation) |
|
151 @param role data role (integer) |
|
152 @return header data (QVariant) |
|
153 """ |
|
154 if orientation == Qt.Horizontal and role == Qt.DisplayRole: |
|
155 try: |
|
156 return QVariant(self.__headers[section]) |
|
157 except IndexError: |
|
158 pass |
|
159 return QAbstractItemModel.headerData(self, section, orientation, role) |
|
160 |
|
161 def data(self, index, role = Qt.DisplayRole): |
|
162 """ |
|
163 Public method to get data from the model. |
|
164 |
|
165 @param index index of bookmark to get data for (QModelIndex) |
|
166 @param role data role (integer) |
|
167 @return bookmark data (QVariant) |
|
168 """ |
|
169 if not index.isValid() or index.model() != self: |
|
170 return QVariant() |
|
171 |
|
172 bookmarkNode = self.node(index) |
|
173 if role in [Qt.EditRole, Qt.DisplayRole]: |
|
174 if bookmarkNode.type() == BookmarkNode.Separator: |
|
175 if index.column() == 0: |
|
176 return QVariant(50 * '\xB7') |
|
177 elif index.column() == 1: |
|
178 return QVariant("") |
|
179 |
|
180 if index.column() == 0: |
|
181 return QVariant(bookmarkNode.title) |
|
182 elif index.column() == 1: |
|
183 return QVariant(bookmarkNode.url) |
|
184 |
|
185 elif role == self.UrlRole: |
|
186 return QVariant(QUrl(bookmarkNode.url)) |
|
187 |
|
188 elif role == self.UrlStringRole: |
|
189 return QVariant(bookmarkNode.url) |
|
190 |
|
191 elif role == self.TypeRole: |
|
192 return QVariant(bookmarkNode.type()) |
|
193 |
|
194 elif role == self.SeparatorRole: |
|
195 return QVariant(bookmarkNode.type() == BookmarkNode.Separator) |
|
196 |
|
197 elif role == Qt.DecorationRole: |
|
198 if index.column() == 0: |
|
199 if bookmarkNode.type() == BookmarkNode.Folder: |
|
200 return QVariant(UI.PixmapCache.getIcon("dirOpen.png")) |
|
201 return QVariant(Helpviewer.HelpWindow.HelpWindow.icon( |
|
202 QUrl(bookmarkNode.url))) |
|
203 |
|
204 return QVariant() |
|
205 |
|
206 def columnCount(self, parent = QModelIndex()): |
|
207 """ |
|
208 Public method to get the number of columns. |
|
209 |
|
210 @param parent index of parent (QModelIndex) |
|
211 @return number of columns (integer) |
|
212 """ |
|
213 if parent.column() > 0: |
|
214 return 0 |
|
215 else: |
|
216 return len(self.__headers) |
|
217 |
|
218 def rowCount(self, parent = QModelIndex()): |
|
219 """ |
|
220 Public method to determine the number of rows. |
|
221 |
|
222 @param parent index of parent (QModelIndex) |
|
223 @return number of rows (integer) |
|
224 """ |
|
225 if parent.column() > 0: |
|
226 return 0 |
|
227 |
|
228 if not parent.isValid(): |
|
229 return len(self.__bookmarksManager.bookmarks().children()) |
|
230 |
|
231 itm = parent.internalPointer() |
|
232 return len(itm.children()) |
|
233 |
|
234 def index(self, row, column, parent = QModelIndex()): |
|
235 """ |
|
236 Public method to get a model index for a node cell. |
|
237 |
|
238 @param row row number (integer) |
|
239 @param column column number (integer) |
|
240 @param parent index of the parent (QModelIndex) |
|
241 @return index (QModelIndex) |
|
242 """ |
|
243 if row < 0 or column < 0 or \ |
|
244 row >= self.rowCount(parent) or column >= self.columnCount(parent): |
|
245 return QModelIndex() |
|
246 |
|
247 parentNode = self.node(parent) |
|
248 return self.createIndex(row, column, parentNode.children()[row]) |
|
249 |
|
250 def parent(self, index = QModelIndex()): |
|
251 """ |
|
252 Public method to get the index of the parent node. |
|
253 |
|
254 @param index index of the child node (QModelIndex) |
|
255 @return index of the parent node (QModelIndex) |
|
256 """ |
|
257 if not index.isValid(): |
|
258 return QModelIndex() |
|
259 |
|
260 itemNode = self.node(index) |
|
261 if itemNode is None: |
|
262 parentNode = None |
|
263 else: |
|
264 parentNode = itemNode.parent() |
|
265 |
|
266 if parentNode is None or parentNode == self.__bookmarksManager.bookmarks(): |
|
267 return QModelIndex() |
|
268 |
|
269 # get the parent's row |
|
270 grandParentNode = parentNode.parent() |
|
271 parentRow = grandParentNode.children().index(parentNode) |
|
272 return self.createIndex(parentRow, 0, parentNode) |
|
273 |
|
274 def hasChildren(self, parent = QModelIndex()): |
|
275 """ |
|
276 Public method to check, if a parent node has some children. |
|
277 |
|
278 @param parent index of the parent node (QModelIndex) |
|
279 @return flag indicating the presence of children (boolean) |
|
280 """ |
|
281 if not parent.isValid(): |
|
282 return True |
|
283 |
|
284 parentNode = self.node(parent) |
|
285 return parentNode.type() == BookmarkNode.Folder |
|
286 |
|
287 def flags(self, index): |
|
288 """ |
|
289 Public method to get flags for a node cell. |
|
290 |
|
291 @param index index of the node cell (QModelIndex) |
|
292 @return flags (Qt.ItemFlags) |
|
293 """ |
|
294 if not index.isValid(): |
|
295 return Qt.NoItemFlags |
|
296 |
|
297 node = self.node(index) |
|
298 type_ = node.type() |
|
299 flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled |
|
300 |
|
301 if self.hasChildren(index): |
|
302 flags |= Qt.ItemIsDropEnabled |
|
303 |
|
304 if node == self.__bookmarksManager.menu() or \ |
|
305 node == self.__bookmarksManager.toolbar(): |
|
306 return flags |
|
307 |
|
308 flags |= Qt.ItemIsDragEnabled |
|
309 |
|
310 if (index.column() == 0 and type_ != BookmarkNode.Separator) or \ |
|
311 (index.column() == 1 and type_ == BookmarkNode.Bookmark): |
|
312 flags |= Qt.ItemIsEditable |
|
313 |
|
314 return flags |
|
315 |
|
316 def supportedDropActions(self): |
|
317 """ |
|
318 Public method to report the supported drop actions. |
|
319 |
|
320 @return supported drop actions (Qt.DropAction) |
|
321 """ |
|
322 return Qt.CopyAction | Qt.MoveAction |
|
323 |
|
324 def mimeTypes(self): |
|
325 """ |
|
326 Public method to report the supported mime types. |
|
327 |
|
328 @return supported mime types (list of strings) |
|
329 """ |
|
330 return [self.MIMETYPE, "text/uri-list"] |
|
331 |
|
332 def mimeData(self, indexes): |
|
333 """ |
|
334 Public method to return the mime data. |
|
335 |
|
336 @param indexes list of indexes (QModelIndexList) |
|
337 @return mime data (QMimeData) |
|
338 """ |
|
339 data = QByteArray() |
|
340 stream = QDataStream(data, QIODevice.WriteOnly) |
|
341 urls = [] |
|
342 |
|
343 for index in indexes: |
|
344 if index.column() != 0 or not index.isValid(): |
|
345 continue |
|
346 |
|
347 encodedData = QByteArray() |
|
348 buffer = QBuffer(encodedData) |
|
349 buffer.open(QIODevice.ReadWrite) |
|
350 writer = XbelWriter() |
|
351 parentNode = self.node(index) |
|
352 writer.write(buffer, parentNode) |
|
353 stream << encodedData |
|
354 urls.append(index.data(self.UrlRole).toUrl()) |
|
355 |
|
356 mdata = QMimeData() |
|
357 mdata.setData(self.MIMETYPE, data) |
|
358 mdata.setUrls(urls) |
|
359 return mdata |
|
360 |
|
361 def dropMimeData(self, data, action, row, column, parent): |
|
362 """ |
|
363 Public method to accept the mime data of a drop action. |
|
364 |
|
365 @param data reference to the mime data (QMimeData) |
|
366 @param action drop action requested (Qt.DropAction) |
|
367 @param row row number (integer) |
|
368 @param column column number (integer) |
|
369 @param parent index of the parent node (QModelIndex) |
|
370 @return flag indicating successful acceptance of the data (boolean) |
|
371 """ |
|
372 if action == Qt.IgnoreAction: |
|
373 return True |
|
374 |
|
375 if column > 0: |
|
376 return False |
|
377 |
|
378 parentNode = self.node(parent) |
|
379 |
|
380 if not data.hasFormat(self.MIMETYPE): |
|
381 if not data.hasUrls(): |
|
382 return False |
|
383 |
|
384 node = BookmarkNode(BookmarkNode.Bookmark, parentNode) |
|
385 node.url = unicode(data.urls()[0].toEncoded()) |
|
386 |
|
387 if data.hasText(): |
|
388 node.title = data.text() |
|
389 else: |
|
390 node.title = node.url |
|
391 |
|
392 self.__bookmarksManager.addBookmark(parentNode, node, row) |
|
393 return True |
|
394 |
|
395 ba = data.data(self.MIMETYPE) |
|
396 stream = QDataStream(ba, QIODevice.ReadOnly) |
|
397 if stream.atEnd(): |
|
398 return False |
|
399 |
|
400 undoStack = self.__bookmarksManager.undoRedoStack() |
|
401 undoStack.beginMacro("Move Bookmarks") |
|
402 |
|
403 while not stream.atEnd(): |
|
404 encodedData = QByteArray() |
|
405 stream >> encodedData |
|
406 buffer = QBuffer(encodedData) |
|
407 buffer.open(QIODevice.ReadOnly) |
|
408 |
|
409 reader = XbelReader() |
|
410 rootNode = reader.read(buffer) |
|
411 for bookmarkNode in rootNode.children(): |
|
412 rootNode.remove(bookmarkNode) |
|
413 row = max(0, row) |
|
414 self.__bookmarksManager.addBookmark(parentNode, bookmarkNode, row) |
|
415 self.__endMacro = True |
|
416 |
|
417 return True |
|
418 |
|
419 def setData(self, index, value, role = Qt.EditRole): |
|
420 """ |
|
421 Public method to set the data of a node cell. |
|
422 |
|
423 @param index index of the node cell (QModelIndex) |
|
424 @param value value to be set (QVariant) |
|
425 @param role role of the data (integer) |
|
426 @return flag indicating success (boolean) |
|
427 """ |
|
428 if not index.isValid() or (self.flags(index) & Qt.ItemIsEditable) == 0: |
|
429 return False |
|
430 |
|
431 item = self.node(index) |
|
432 |
|
433 if role in (Qt.EditRole, Qt.DisplayRole): |
|
434 if index.column() == 0: |
|
435 self.__bookmarksManager.setTitle(item, value.toString()) |
|
436 elif index.column() == 1: |
|
437 self.__bookmarksManager.setUrl(item, value.toString()) |
|
438 else: |
|
439 return False |
|
440 |
|
441 elif role == BookmarksModel.UrlRole: |
|
442 self.__bookmarksManager.setUrl(item, value.toUrl().toString()) |
|
443 |
|
444 elif role == BookmarksModel.UrlStringRole: |
|
445 self.__bookmarksManager.setUrl(item, value.toString()) |
|
446 |
|
447 else: |
|
448 return False |
|
449 |
|
450 return True |
|
451 |
|
452 def node(self, index): |
|
453 """ |
|
454 Public method to get a bookmark node given it's index. |
|
455 |
|
456 @param index index of the node (QModelIndex) |
|
457 @return bookmark node (BookmarkNode) |
|
458 """ |
|
459 itemNode = index.internalPointer() |
|
460 if itemNode is None: |
|
461 return self.__bookmarksManager.bookmarks() |
|
462 else: |
|
463 return itemNode |