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 }