src/eric7/WebBrowser/Bookmarks/BookmarksModel.py

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

eric ide

mercurial