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