File indexing completed on 2024-05-12 04:57:57

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2014-2017 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "bookmarksmodel.h"
0019 #include "bookmarkitem.h"
0020 #include "bookmarks.h"
0021 
0022 #include <QApplication>
0023 #include <QTimer>
0024 #include <QStyle>
0025 #include <QIODevice>
0026 
0027 //#define BOOKMARKSMODEL_DEBUG
0028 
0029 #ifdef BOOKMARKSMODEL_DEBUG
0030 #include "modeltest.h"
0031 #endif
0032 
0033 BookmarksModel::BookmarksModel(BookmarkItem* root, Bookmarks* bookmarks, QObject* parent)
0034     : QAbstractItemModel(parent)
0035     , m_root(root)
0036     , m_bookmarks(bookmarks)
0037 {
0038     if (m_bookmarks) {
0039         connect(m_bookmarks, &Bookmarks::bookmarkChanged, this, &BookmarksModel::bookmarkChanged);
0040     }
0041 
0042 #ifdef BOOKMARKSMODEL_DEBUG
0043     new ModelTest(this, this);
0044 #endif
0045 }
0046 
0047 void BookmarksModel::addBookmark(BookmarkItem* parent, int row, BookmarkItem* item)
0048 {
0049     Q_ASSERT(parent);
0050     Q_ASSERT(item);
0051     Q_ASSERT(row >= 0);
0052     Q_ASSERT(row <= parent->children().count());
0053 
0054     beginInsertRows(index(parent), row, row);
0055     parent->addChild(item, row);
0056     endInsertRows();
0057 }
0058 
0059 void BookmarksModel::removeBookmark(BookmarkItem* item)
0060 {
0061     Q_ASSERT(item);
0062     Q_ASSERT(item->parent());
0063 
0064     int idx = item->parent()->children().indexOf(item);
0065 
0066     beginRemoveRows(index(item->parent()), idx, idx);
0067     item->parent()->removeChild(item);
0068     endRemoveRows();
0069 }
0070 
0071 Qt::ItemFlags BookmarksModel::flags(const QModelIndex &index) const
0072 {
0073     BookmarkItem* itm = item(index);
0074 
0075     if (!index.isValid() || !itm) {
0076         return Qt::NoItemFlags;
0077     }
0078 
0079     Qt::ItemFlags flags =  Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0080 
0081     if (itm->isFolder()) {
0082         flags |= Qt::ItemIsDropEnabled;
0083     }
0084 
0085     if (m_bookmarks && m_bookmarks->canBeModified(itm)) {
0086         flags |= Qt::ItemIsDragEnabled;
0087     }
0088 
0089     return flags;
0090 }
0091 
0092 QVariant BookmarksModel::data(const QModelIndex &index, int role) const
0093 {
0094     BookmarkItem* itm = item(index);
0095 
0096     if (!itm) {
0097         return {};
0098     }
0099 
0100     switch (role) {
0101     case TypeRole:
0102         return itm->type();
0103     case UrlRole:
0104         return itm->url();
0105     case UrlStringRole:
0106         return itm->urlString();
0107     case TitleRole:
0108         return itm->title();
0109     case DescriptionRole:
0110         return itm->description();
0111     case KeywordRole:
0112         return itm->keyword();
0113     case VisitCountRole:
0114         return -1;
0115     case ExpandedRole:
0116         return itm->isExpanded();
0117     case SidebarExpandedRole:
0118         return itm->isSidebarExpanded();
0119     case Qt::ToolTipRole:
0120         if (index.column() == 0 && itm->isUrl()) {
0121             return QSL("%1\n%2").arg(itm->title(), QString::fromUtf8(itm->url().toEncoded()));
0122         }
0123         // fallthrough
0124     case Qt::DisplayRole:
0125         switch (index.column()) {
0126         case 0:
0127             return itm->title();
0128         case 1:
0129             return QString::fromUtf8(itm->url().toEncoded());
0130         default:
0131             return {};
0132         }
0133     case Qt::DecorationRole:
0134         if (index.column() == 0) {
0135             return itm->icon();
0136         }
0137         return {};
0138     default:
0139         return {};
0140     }
0141 }
0142 
0143 QVariant BookmarksModel::headerData(int section, Qt::Orientation orientation, int role) const
0144 {
0145     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
0146         switch (section) {
0147         case 0:
0148             return tr("Title");
0149         case 1:
0150             return tr("Address");
0151         }
0152     }
0153 
0154     return QAbstractItemModel::headerData(section, orientation, role);
0155 }
0156 
0157 int BookmarksModel::rowCount(const QModelIndex &parent) const
0158 {
0159     if (parent.column() > 0) {
0160         return 0;
0161     }
0162 
0163     BookmarkItem* itm = item(parent);
0164     return itm->children().count();
0165 }
0166 
0167 int BookmarksModel::columnCount(const QModelIndex &parent) const
0168 {
0169     if (parent.column() > 0) {
0170         return 0;
0171     }
0172 
0173     return 2;
0174 }
0175 
0176 bool BookmarksModel::hasChildren(const QModelIndex &parent) const
0177 {
0178     BookmarkItem* itm = item(parent);
0179     return !itm->children().isEmpty();
0180 }
0181 
0182 Qt::DropActions BookmarksModel::supportedDropActions() const
0183 {
0184     return Qt::CopyAction | Qt::MoveAction;
0185 }
0186 
0187 #define MIMETYPE QLatin1String("application/falkon.bookmarks")
0188 
0189 QStringList BookmarksModel::mimeTypes() const
0190 {
0191     QStringList types;
0192     types.append(MIMETYPE);
0193     return types;
0194 }
0195 
0196 QMimeData* BookmarksModel::mimeData(const QModelIndexList &indexes) const
0197 {
0198     auto* mimeData = new QMimeData();
0199     QByteArray encodedData;
0200 
0201     QDataStream stream(&encodedData, QIODevice::WriteOnly);
0202 
0203     for (const QModelIndex &index : indexes) {
0204         // If item's parent (=folder) is also selected, we will just move the whole folder
0205         if (index.isValid() && index.column() == 0 && !indexes.contains(index.parent())) {
0206             stream << index.row() << (quintptr) index.internalPointer();
0207         }
0208     }
0209 
0210     mimeData->setData(MIMETYPE, encodedData);
0211     return mimeData;
0212 }
0213 
0214 bool BookmarksModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
0215 {
0216     Q_UNUSED(column)
0217 
0218     if (action == Qt::IgnoreAction) {
0219         return true;
0220     }
0221 
0222     if (!m_bookmarks || !data->hasFormat(MIMETYPE) || !parent.isValid()) {
0223         return false;
0224     }
0225 
0226     BookmarkItem* parentItm = item(parent);
0227     Q_ASSERT(parentItm->isFolder());
0228 
0229     QByteArray encodedData = data->data(MIMETYPE);
0230     QDataStream stream(&encodedData, QIODevice::ReadOnly);
0231     QList<BookmarkItem*> items;
0232 
0233     while (!stream.atEnd()) {
0234         int row;
0235         quintptr ptr;
0236 
0237         stream >> row >> ptr;
0238 
0239         QModelIndex index = createIndex(row, 0, (void*) ptr);
0240         BookmarkItem* itm = item(index);
0241 
0242         Q_ASSERT(index.isValid());
0243         Q_ASSERT(itm != m_bookmarks->rootItem());
0244 
0245         // Cannot move bookmark to itself
0246         if (itm == parentItm) {
0247             return false;
0248         }
0249 
0250         items.append(itm);
0251     }
0252 
0253     row = qMax(row, 0);
0254 
0255     for (BookmarkItem* itm : std::as_const(items)) {
0256         // If we are moving an item through the folder and item is above the row to insert,
0257         // we must decrease row by one (by the dropped folder)
0258         if (itm->parent() == parentItm && itm->parent()->children().indexOf(itm) < row) {
0259             row--;
0260         }
0261 
0262         m_bookmarks->removeBookmark(itm);
0263         m_bookmarks->insertBookmark(parentItm, row++, itm);
0264     }
0265 
0266     return true;
0267 }
0268 
0269 QModelIndex BookmarksModel::parent(const QModelIndex &child) const
0270 {
0271     if (!child.isValid()) {
0272         return {};
0273     }
0274 
0275     BookmarkItem* itm = item(child);
0276     return index(itm->parent());
0277 }
0278 
0279 QModelIndex BookmarksModel::index(int row, int column, const QModelIndex &parent) const
0280 {
0281     if (!hasIndex(row, column, parent)) {
0282         return {};
0283     }
0284 
0285     BookmarkItem* parentItem = item(parent);
0286     return createIndex(row, column, parentItem->children().at(row));
0287 }
0288 
0289 QModelIndex BookmarksModel::index(BookmarkItem* item, int column) const
0290 {
0291     BookmarkItem* parent = item->parent();
0292 
0293     if (!parent) {
0294         return {};
0295     }
0296 
0297     return createIndex(parent->children().indexOf(item), column, item);
0298 }
0299 
0300 BookmarkItem* BookmarksModel::item(const QModelIndex &index) const
0301 {
0302     auto* itm = static_cast<BookmarkItem*>(index.internalPointer());
0303     return itm ? itm : m_root;
0304 }
0305 
0306 void BookmarksModel::bookmarkChanged(BookmarkItem* item)
0307 {
0308     QModelIndex idx = index(item);
0309     Q_EMIT dataChanged(idx, idx);
0310 }
0311 
0312 
0313 // BookmarksFilterModel
0314 BookmarksFilterModel::BookmarksFilterModel(QAbstractItemModel* parent)
0315     : QSortFilterProxyModel(parent)
0316 {
0317     setSourceModel(parent);
0318     setFilterCaseSensitivity(Qt::CaseInsensitive);
0319 
0320     m_filterTimer = new QTimer(this);
0321     m_filterTimer->setSingleShot(true);
0322     m_filterTimer->setInterval(300);
0323 
0324     connect(m_filterTimer, &QTimer::timeout, this, &BookmarksFilterModel::startFiltering);
0325 }
0326 
0327 void BookmarksFilterModel::setFilterFixedString(const QString &pattern)
0328 {
0329     m_pattern = pattern;
0330 
0331     m_filterTimer->start();
0332 }
0333 
0334 bool BookmarksFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
0335 {
0336     const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
0337 
0338     if (index.data(BookmarksModel::TypeRole).toInt() == BookmarkItem::Folder) {
0339         return true;
0340     }
0341 
0342     return (index.data(BookmarksModel::TitleRole).toString().contains(m_pattern, filterCaseSensitivity()) ||
0343             index.data(BookmarksModel::UrlStringRole).toString().contains(m_pattern, filterCaseSensitivity()) ||
0344             index.data(BookmarksModel::DescriptionRole).toString().contains(m_pattern, filterCaseSensitivity()) ||
0345             index.data(BookmarksModel::KeywordRole).toString().compare(m_pattern, filterCaseSensitivity()) == 0);
0346 }
0347 
0348 void BookmarksFilterModel::startFiltering()
0349 {
0350     QSortFilterProxyModel::setFilterFixedString(m_pattern);
0351 }
0352 
0353 BookmarksButtonMimeData::BookmarksButtonMimeData()
0354     : QMimeData()
0355 {
0356 }
0357 
0358 BookmarkItem *BookmarksButtonMimeData::item() const
0359 {
0360     return m_item;
0361 }
0362 
0363 void BookmarksButtonMimeData::setBookmarkItem(BookmarkItem *item)
0364 {
0365     m_item = item;
0366 }
0367 
0368 bool BookmarksButtonMimeData::hasFormat(const QString &format) const
0369 {
0370     return mimeType() == format;
0371 }
0372 
0373 QStringList BookmarksButtonMimeData::formats() const
0374 {
0375     return {mimeType()};
0376 }
0377 
0378 QString BookmarksButtonMimeData::mimeType()
0379 {
0380     return QSL("application/falkon.bookmarktoolbutton.bookmarkitem");
0381 }