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
38const quint32 VERSION = 0xe53798;
39const QLatin1String MIMETYPE("application/bookmarks.assistant");
40
41BookmarkModel::BookmarkModel()
42 : QAbstractItemModel()
43 , m_folder(false)
44 , m_editable(false)
45 , rootItem(nullptr)
46{
47}
48
49BookmarkModel::~BookmarkModel()
50{
51 delete rootItem;
52}
53
54QByteArray
55BookmarkModel::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
68void
69BookmarkModel::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* menu = 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
121void
122BookmarkModel::setItemsEditable(bool editable)
123{
124 m_editable = editable;
125}
126
127void
128BookmarkModel::expandFoldersIfNeeeded(QTreeView *treeView)
129{
130 for (const QModelIndex &index : qAsConst(t&: cache))
131 treeView->setExpanded(index, expand: index.data(arole: UserRoleExpanded).toBool());
132}
133
134QModelIndex
135BookmarkModel::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
146bool
147BookmarkModel::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
165int
166BookmarkModel::rowCount(const QModelIndex &index) const
167{
168 if (BookmarkItem *item = itemFromIndex(index))
169 return item->childCount();
170 return 0;
171}
172
173int
174BookmarkModel::columnCount(const QModelIndex &/*index*/) const
175{
176 return 2;
177}
178
179QModelIndex
180BookmarkModel::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
194QModelIndex
195BookmarkModel::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
207Qt::DropActions
208BookmarkModel::supportedDropActions () const
209{
210 return /* Qt::CopyAction | */Qt::MoveAction;
211}
212
213Qt::ItemFlags
214BookmarkModel::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
233QVariant
234BookmarkModel::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
259void 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
267bool
268BookmarkModel::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
289QVariant
290BookmarkModel::headerData(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
298QModelIndex
299BookmarkModel::indexFromItem(BookmarkItem *item) const
300{
301 return cache.value(akey: item, adefaultValue: QModelIndex());
302}
303
304BookmarkItem*
305BookmarkModel::itemFromIndex(const QModelIndex &index) const
306{
307 if (index.isValid())
308 return static_cast<BookmarkItem*>(index.internalPointer());
309 return rootItem;
310}
311
312QList<QPersistentModelIndex>
313BookmarkModel::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
324bool
325BookmarkModel::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 &current = 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
343bool
344BookmarkModel::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
355QStringList
356BookmarkModel::mimeTypes() const
357{
358 return QStringList() << MIMETYPE;
359}
360
361QMimeData*
362BookmarkModel::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
380bool
381BookmarkModel::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 &current = 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
411void
412BookmarkModel::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
419QModelIndexList
420BookmarkModel::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
432void
433BookmarkModel::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

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