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