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
14const quint32 VERSION = 0xe53798;
15const QLatin1String MIMETYPE("application/bookmarks.assistant");
16
17BookmarkModel::BookmarkModel()
18 : QAbstractItemModel()
19 , m_folder(false)
20 , m_editable(false)
21 , rootItem(nullptr)
22{
23}
24
25BookmarkModel::~BookmarkModel()
26{
27 delete rootItem;
28}
29
30QByteArray
31BookmarkModel::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
44void
45BookmarkModel::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* menu = 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
97void
98BookmarkModel::setItemsEditable(bool editable)
99{
100 m_editable = editable;
101}
102
103void
104BookmarkModel::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
110QModelIndex
111BookmarkModel::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
122bool
123BookmarkModel::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
141int
142BookmarkModel::rowCount(const QModelIndex &index) const
143{
144 if (BookmarkItem *item = itemFromIndex(index))
145 return item->childCount();
146 return 0;
147}
148
149int
150BookmarkModel::columnCount(const QModelIndex &/*index*/) const
151{
152 return 2;
153}
154
155QModelIndex
156BookmarkModel::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
170QModelIndex
171BookmarkModel::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
183Qt::DropActions
184BookmarkModel::supportedDropActions () const
185{
186 return /* Qt::CopyAction | */Qt::MoveAction;
187}
188
189Qt::ItemFlags
190BookmarkModel::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
209QVariant
210BookmarkModel::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
235void 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
243bool
244BookmarkModel::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
265QVariant
266BookmarkModel::headerData(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
274QModelIndex
275BookmarkModel::indexFromItem(BookmarkItem *item) const
276{
277 return cache.value(key: item, defaultValue: QModelIndex());
278}
279
280BookmarkItem*
281BookmarkModel::itemFromIndex(const QModelIndex &index) const
282{
283 if (index.isValid())
284 return static_cast<BookmarkItem*>(index.internalPointer());
285 return rootItem;
286}
287
288QList<QPersistentModelIndex>
289BookmarkModel::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
300bool
301BookmarkModel::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 &current = 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
319bool
320BookmarkModel::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
331QStringList
332BookmarkModel::mimeTypes() const
333{
334 return QStringList() << MIMETYPE;
335}
336
337QMimeData*
338BookmarkModel::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
356bool
357BookmarkModel::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 &current = 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
387void
388BookmarkModel::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
395QModelIndexList
396BookmarkModel::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
408void
409BookmarkModel::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

source code of qttools/src/assistant/assistant/bookmarkmodel.cpp