1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | #include "bookmarkmodel.h" |
4 | #include "bookmarkitem.h" |
5 | |
6 | #include <QtCore/QIODevice> |
7 | #include <QtCore/QMimeData> |
8 | #include <QtCore/QStack> |
9 | |
10 | #include <QtWidgets/QApplication> |
11 | #include <QtWidgets/QStyle> |
12 | #include <QtWidgets/QTreeView> |
13 | |
14 | const quint32 VERSION = 0xe53798; |
15 | const QLatin1String MIMETYPE("application/bookmarks.assistant" ); |
16 | |
17 | BookmarkModel::BookmarkModel() |
18 | : QAbstractItemModel() |
19 | , m_folder(false) |
20 | , m_editable(false) |
21 | , rootItem(nullptr) |
22 | { |
23 | } |
24 | |
25 | BookmarkModel::~BookmarkModel() |
26 | { |
27 | delete rootItem; |
28 | } |
29 | |
30 | QByteArray |
31 | BookmarkModel::bookmarks() const |
32 | { |
33 | QByteArray ba; |
34 | QDataStream stream(&ba, QIODevice::WriteOnly); |
35 | stream << qint32(VERSION); |
36 | |
37 | const QModelIndex &root = index(row: 0,column: 0, index: QModelIndex()).parent(); |
38 | for (int i = 0; i < rowCount(index: root); ++i) |
39 | collectItems(parent: index(row: i, column: 0, index: root), depth: 0, stream: &stream); |
40 | |
41 | return ba; |
42 | } |
43 | |
44 | void |
45 | BookmarkModel::setBookmarks(const QByteArray &bookmarks) |
46 | { |
47 | beginResetModel(); |
48 | |
49 | delete rootItem; |
50 | folderIcon = QApplication::style()->standardIcon(standardIcon: QStyle::SP_DirClosedIcon); |
51 | bookmarkIcon = QIcon(QLatin1String(":/qt-project.org/assistant/images/bookmark.png" )); |
52 | |
53 | rootItem = new BookmarkItem(DataVector() << tr(s: "Name" ) << tr(s: "Address" ) |
54 | << true); |
55 | |
56 | QStack<BookmarkItem*> parents; |
57 | QDataStream stream(bookmarks); |
58 | |
59 | quint32 version; |
60 | stream >> version; |
61 | if (version < VERSION) { |
62 | stream.device()->seek(pos: 0); |
63 | BookmarkItem* toolbar = new BookmarkItem(DataVector() << tr(s: "Bookmarks Toolbar" ) |
64 | << QLatin1String("Folder" ) << true); |
65 | rootItem->addChild(child: toolbar); |
66 | |
67 | BookmarkItem* = new BookmarkItem(DataVector() << tr(s: "Bookmarks Menu" ) |
68 | << QLatin1String("Folder" ) << true); |
69 | rootItem->addChild(child: menu); |
70 | parents.push(t: menu); |
71 | } else { |
72 | parents.push(t: rootItem); |
73 | } |
74 | |
75 | qint32 depth; |
76 | bool expanded; |
77 | QString name, url; |
78 | while (!stream.atEnd()) { |
79 | stream >> depth >> name >> url >> expanded; |
80 | while ((parents.size() - 1) != depth) |
81 | parents.pop(); |
82 | |
83 | BookmarkItem *item = new BookmarkItem(DataVector() << name << url << expanded); |
84 | if (url == QLatin1String("Folder" )) { |
85 | parents.top()->addChild(child: item); |
86 | parents.push(t: item); |
87 | } else { |
88 | parents.top()->addChild(child: item); |
89 | } |
90 | } |
91 | |
92 | cache.clear(); |
93 | setupCache(index(row: 0,column: 0, index: QModelIndex().parent())); |
94 | endResetModel(); |
95 | } |
96 | |
97 | void |
98 | BookmarkModel::setItemsEditable(bool editable) |
99 | { |
100 | m_editable = editable; |
101 | } |
102 | |
103 | void |
104 | BookmarkModel::expandFoldersIfNeeeded(QTreeView *treeView) |
105 | { |
106 | for (QModelIndex index : std::as_const(t&: cache)) |
107 | treeView->setExpanded(index, expand: index.data(arole: UserRoleExpanded).toBool()); |
108 | } |
109 | |
110 | QModelIndex |
111 | BookmarkModel::addItem(const QModelIndex &parent, bool isFolder) |
112 | { |
113 | m_folder = isFolder; |
114 | QModelIndex next; |
115 | if (insertRow(arow: rowCount(index: parent), aparent: parent)) |
116 | next = index(row: rowCount(index: parent) - 1, column: 0, index: parent); |
117 | m_folder = false; |
118 | |
119 | return next; |
120 | } |
121 | |
122 | bool |
123 | BookmarkModel::removeItem(const QModelIndex &index) |
124 | { |
125 | if (!index.isValid()) |
126 | return false; |
127 | |
128 | QModelIndexList indexes; |
129 | if (rowCount(index) > 0) |
130 | indexes = collectItems(parent: index); |
131 | indexes.append(t: index); |
132 | |
133 | for (const QModelIndex &itemToRemove : std::as_const(t&: indexes)) { |
134 | if (!removeRow(arow: itemToRemove.row(), aparent: itemToRemove.parent())) |
135 | return false; |
136 | cache.remove(key: itemFromIndex(index: itemToRemove)); |
137 | } |
138 | return true; |
139 | } |
140 | |
141 | int |
142 | BookmarkModel::rowCount(const QModelIndex &index) const |
143 | { |
144 | if (BookmarkItem *item = itemFromIndex(index)) |
145 | return item->childCount(); |
146 | return 0; |
147 | } |
148 | |
149 | int |
150 | BookmarkModel::columnCount(const QModelIndex &/*index*/) const |
151 | { |
152 | return 2; |
153 | } |
154 | |
155 | QModelIndex |
156 | BookmarkModel::parent(const QModelIndex &index) const |
157 | { |
158 | if (!index.isValid()) |
159 | return QModelIndex(); |
160 | |
161 | if (BookmarkItem *childItem = itemFromIndex(index)) { |
162 | if (BookmarkItem *parent = childItem->parent()) { |
163 | if (parent != rootItem) |
164 | return createIndex(arow: parent->childNumber(), acolumn: 0, adata: parent); |
165 | } |
166 | } |
167 | return QModelIndex(); |
168 | } |
169 | |
170 | QModelIndex |
171 | BookmarkModel::index(int row, int column, const QModelIndex &index) const |
172 | { |
173 | if (index.isValid() && (index.column() != 0 && index.column() != 1)) |
174 | return QModelIndex(); |
175 | |
176 | if (BookmarkItem *parent = itemFromIndex(index)) { |
177 | if (BookmarkItem *childItem = parent->child(number: row)) |
178 | return createIndex(arow: row, acolumn: column, adata: childItem); |
179 | } |
180 | return QModelIndex(); |
181 | } |
182 | |
183 | Qt::DropActions |
184 | BookmarkModel::supportedDropActions () const |
185 | { |
186 | return /* Qt::CopyAction | */Qt::MoveAction; |
187 | } |
188 | |
189 | Qt::ItemFlags |
190 | BookmarkModel::flags(const QModelIndex &index) const |
191 | { |
192 | if (!index.isValid()) |
193 | return Qt::NoItemFlags; |
194 | |
195 | Qt::ItemFlags defaultFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; |
196 | |
197 | if (m_editable) |
198 | defaultFlags |= Qt::ItemIsEditable; |
199 | |
200 | if (itemFromIndex(index) && index.data(arole: UserRoleFolder).toBool()) { |
201 | if (index.column() > 0) |
202 | return defaultFlags &~ Qt::ItemIsEditable; |
203 | return defaultFlags | Qt::ItemIsDropEnabled; |
204 | } |
205 | |
206 | return defaultFlags | Qt::ItemIsDragEnabled; |
207 | } |
208 | |
209 | QVariant |
210 | BookmarkModel::data(const QModelIndex &index, int role) const |
211 | { |
212 | if (index.isValid()) { |
213 | if (BookmarkItem *item = itemFromIndex(index)) { |
214 | switch (role) { |
215 | case Qt::EditRole: |
216 | case Qt::DisplayRole: |
217 | if (index.data(arole: UserRoleFolder).toBool() && index.column() == 1) |
218 | return QString(); |
219 | return item->data(column: index.column()); |
220 | |
221 | case Qt::DecorationRole: |
222 | if (index.column() == 0) |
223 | return index.data(arole: UserRoleFolder).toBool() |
224 | ? folderIcon : bookmarkIcon; |
225 | break; |
226 | |
227 | default: |
228 | return item->data(column: role); |
229 | } |
230 | } |
231 | } |
232 | return QVariant(); |
233 | } |
234 | |
235 | void BookmarkModel::setData(const QModelIndex &index, const DataVector &data) |
236 | { |
237 | if (BookmarkItem *item = itemFromIndex(index)) { |
238 | item->setData(data); |
239 | emit dataChanged(topLeft: index, bottomRight: index); |
240 | } |
241 | } |
242 | |
243 | bool |
244 | BookmarkModel::setData(const QModelIndex &index, const QVariant &value, int role) |
245 | { |
246 | bool result = false; |
247 | if (role != Qt::EditRole && role != UserRoleExpanded) |
248 | return result; |
249 | |
250 | if (BookmarkItem *item = itemFromIndex(index)) { |
251 | if (role == Qt::EditRole) { |
252 | const bool isFolder = index.data(arole: UserRoleFolder).toBool(); |
253 | if (!isFolder || (isFolder && index.column() == 0)) |
254 | result = item->setData(column: index.column(), value); |
255 | } else if (role == UserRoleExpanded) { |
256 | result = item->setData(column: UserRoleExpanded, value); |
257 | } |
258 | } |
259 | |
260 | if (result) |
261 | emit dataChanged(topLeft: index, bottomRight: index); |
262 | return result; |
263 | } |
264 | |
265 | QVariant |
266 | BookmarkModel::(int section, Qt::Orientation orientation, |
267 | int role) const |
268 | { |
269 | if (rootItem && orientation == Qt::Horizontal && role == Qt::DisplayRole) |
270 | return rootItem->data(column: section); |
271 | return QVariant(); |
272 | } |
273 | |
274 | QModelIndex |
275 | BookmarkModel::indexFromItem(BookmarkItem *item) const |
276 | { |
277 | return cache.value(key: item, defaultValue: QModelIndex()); |
278 | } |
279 | |
280 | BookmarkItem* |
281 | BookmarkModel::itemFromIndex(const QModelIndex &index) const |
282 | { |
283 | if (index.isValid()) |
284 | return static_cast<BookmarkItem*>(index.internalPointer()); |
285 | return rootItem; |
286 | } |
287 | |
288 | QList<QPersistentModelIndex> |
289 | BookmarkModel::indexListFor(const QString &label) const |
290 | { |
291 | QList<QPersistentModelIndex> hits; |
292 | const QModelIndexList &list = collectItems(parent: QModelIndex()); |
293 | for (const QModelIndex &index : list) { |
294 | if (index.data().toString().contains(s: label, cs: Qt::CaseInsensitive)) |
295 | hits.prepend(t: index); // list is reverse sorted |
296 | } |
297 | return hits; |
298 | } |
299 | |
300 | bool |
301 | BookmarkModel::insertRows(int position, int rows, const QModelIndex &parent) |
302 | { |
303 | if (parent.isValid() && !parent.data(arole: UserRoleFolder).toBool()) |
304 | return false; |
305 | |
306 | bool success = false; |
307 | if (BookmarkItem *parentItem = itemFromIndex(index: parent)) { |
308 | beginInsertRows(parent, first: position, last: position + rows - 1); |
309 | success = parentItem->insertChildren(isFolder: m_folder, position, count: rows); |
310 | if (success) { |
311 | const QModelIndex ¤t = index(row: position, column: 0, index: parent); |
312 | cache.insert(key: itemFromIndex(index: current), value: current); |
313 | } |
314 | endInsertRows(); |
315 | } |
316 | return success; |
317 | } |
318 | |
319 | bool |
320 | BookmarkModel::removeRows(int position, int rows, const QModelIndex &index) |
321 | { |
322 | bool success = false; |
323 | if (BookmarkItem *parent = itemFromIndex(index)) { |
324 | beginRemoveRows(parent: index, first: position, last: position + rows - 1); |
325 | success = parent->removeChildren(position, count: rows); |
326 | endRemoveRows(); |
327 | } |
328 | return success; |
329 | } |
330 | |
331 | QStringList |
332 | BookmarkModel::mimeTypes() const |
333 | { |
334 | return QStringList() << MIMETYPE; |
335 | } |
336 | |
337 | QMimeData* |
338 | BookmarkModel::mimeData(const QModelIndexList &indexes) const |
339 | { |
340 | if (indexes.isEmpty()) |
341 | return nullptr; |
342 | |
343 | QByteArray data; |
344 | QDataStream stream(&data, QIODevice::WriteOnly); |
345 | |
346 | for (const QModelIndex &index : indexes) { |
347 | if (index.column() == 0) |
348 | collectItems(parent: index, depth: 0, stream: &stream); |
349 | } |
350 | |
351 | QMimeData *mimeData = new QMimeData(); |
352 | mimeData->setData(mimetype: MIMETYPE, data); |
353 | return mimeData; |
354 | } |
355 | |
356 | bool |
357 | BookmarkModel::dropMimeData(const QMimeData *data, Qt::DropAction action, |
358 | int row, int column, const QModelIndex &parent) |
359 | { |
360 | if (action == Qt::IgnoreAction) |
361 | return true; |
362 | |
363 | if (!data->hasFormat(mimetype: MIMETYPE) || column > 0) |
364 | return false; |
365 | |
366 | QByteArray ba = data->data(mimetype: MIMETYPE); |
367 | QDataStream stream(&ba, QIODevice::ReadOnly); |
368 | while (stream.atEnd()) |
369 | return false; |
370 | |
371 | qint32 depth; |
372 | bool expanded; |
373 | QString name, url; |
374 | while (!stream.atEnd()) { |
375 | stream >> depth >> name >> url >> expanded; |
376 | if (insertRow(arow: qMax(a: 0, b: row), aparent: parent)) { |
377 | const QModelIndex ¤t = index(row: qMax(a: 0, b: row), column: 0, index: parent); |
378 | if (current.isValid()) { |
379 | BookmarkItem* item = itemFromIndex(index: current); |
380 | item->setData(DataVector() << name << url << expanded); |
381 | } |
382 | } |
383 | } |
384 | return true; |
385 | } |
386 | |
387 | void |
388 | BookmarkModel::setupCache(const QModelIndex &parent) |
389 | { |
390 | const QModelIndexList &list = collectItems(parent); |
391 | for (const QModelIndex &index : list) |
392 | cache.insert(key: itemFromIndex(index), value: index); |
393 | } |
394 | |
395 | QModelIndexList |
396 | BookmarkModel::collectItems(const QModelIndex &parent) const |
397 | { |
398 | QModelIndexList list; |
399 | for (int i = rowCount(index: parent) - 1; i >= 0 ; --i) { |
400 | const QModelIndex &next = index(row: i, column: 0, index: parent); |
401 | if (data(index: next, role: UserRoleFolder).toBool()) |
402 | list += collectItems(parent: next); |
403 | list.append(t: next); |
404 | } |
405 | return list; |
406 | } |
407 | |
408 | void |
409 | BookmarkModel::collectItems(const QModelIndex &parent, qint32 depth, |
410 | QDataStream *stream) const |
411 | { |
412 | if (parent.isValid()) { |
413 | *stream << depth; |
414 | *stream << parent.data().toString(); |
415 | *stream << parent.data(arole: UserRoleUrl).toString(); |
416 | *stream << parent.data(arole: UserRoleExpanded).toBool(); |
417 | |
418 | for (int i = 0; i < rowCount(index: parent); ++i) { |
419 | if (parent.data(arole: UserRoleFolder).toBool()) |
420 | collectItems(parent: index(row: i, column: 0 , index: parent), depth: depth + 1, stream); |
421 | } |
422 | } |
423 | } |
424 | |