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