File indexing completed on 2024-04-21 04:58:15
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2009 Pino Toscano <pino@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "konqhistorymodel.h" 0008 0009 #include "konqhistory.h" 0010 #include "konq_historyprovider.h" 0011 0012 #include <KLocalizedString> 0013 #include <kio/global.h> 0014 #include <kprotocolinfo.h> 0015 0016 #include <QHash> 0017 #include <QList> 0018 #include <QLocale> 0019 #include <QIcon> 0020 0021 namespace KHM 0022 { 0023 0024 struct Entry { 0025 enum Type { 0026 History, 0027 Group, 0028 Root 0029 }; 0030 0031 Entry(Type _type) 0032 : type(_type) 0033 {} 0034 0035 virtual ~Entry() 0036 {} 0037 0038 virtual QVariant data(int /*role*/, int /*column*/) const 0039 { 0040 return QVariant(); 0041 } 0042 0043 const Type type; 0044 }; 0045 0046 struct HistoryEntry : public Entry { 0047 HistoryEntry(const KonqHistoryEntry &_entry, GroupEntry *_parent); 0048 0049 QVariant data(int role, int column) const override; 0050 void update(const KonqHistoryEntry &entry); 0051 0052 KonqHistoryEntry entry; 0053 GroupEntry *parent; 0054 QIcon icon; 0055 }; 0056 0057 struct GroupEntry : public Entry { 0058 GroupEntry(const QUrl &_url, const QString &_key); 0059 0060 ~GroupEntry() override 0061 { 0062 qDeleteAll(entries); 0063 } 0064 0065 QVariant data(int role, int column) const override; 0066 HistoryEntry *findChild(const KonqHistoryEntry &entry, int *index = nullptr) const; 0067 QList<QUrl> urls() const; 0068 0069 QList<HistoryEntry *> entries; 0070 QUrl url; 0071 QString key; 0072 QIcon icon; 0073 bool hasFavIcon : 1; 0074 }; 0075 0076 struct RootEntry : public Entry { 0077 RootEntry() 0078 : Entry(Root) 0079 {} 0080 0081 ~RootEntry() override 0082 { 0083 qDeleteAll(groups); 0084 } 0085 0086 QList<GroupEntry *> groups; 0087 QHash<QString, GroupEntry *> groupsByName; 0088 }; 0089 0090 HistoryEntry::HistoryEntry(const KonqHistoryEntry &_entry, GroupEntry *_parent) 0091 : Entry(History), entry(_entry), parent(_parent) 0092 { 0093 parent->entries.append(this); 0094 0095 update(entry); 0096 } 0097 0098 QVariant HistoryEntry::data(int role, int /*column*/) const 0099 { 0100 switch (role) { 0101 case Qt::DisplayRole: { 0102 QString title = entry.title; 0103 if (title.trimmed().isEmpty() || title == entry.url.url()) { 0104 QString path(entry.url.path()); 0105 if (path.isEmpty()) { 0106 path += '/'; 0107 } 0108 title = path; 0109 } 0110 return title; 0111 } 0112 case Qt::DecorationRole: 0113 return icon; 0114 case Qt::ToolTipRole: 0115 return entry.url.url(); 0116 case KonqHistory::TypeRole: 0117 return int(KonqHistory::HistoryType); 0118 case KonqHistory::DetailedToolTipRole: 0119 return i18n("<qt><center><b>%1</b></center><hr />Last visited: %2<br />" 0120 "First visited: %3<br />Number of times visited: %4</qt>", 0121 entry.url.toDisplayString().toHtmlEscaped(), 0122 QLocale().toString(entry.lastVisited), 0123 QLocale().toString(entry.firstVisited), 0124 entry.numberOfTimesVisited); 0125 case KonqHistory::LastVisitedRole: 0126 return entry.lastVisited; 0127 case KonqHistory::UrlRole: 0128 return entry.url; 0129 } 0130 return QVariant(); 0131 } 0132 0133 void HistoryEntry::update(const KonqHistoryEntry &_entry) 0134 { 0135 entry = _entry; 0136 0137 const QString path = entry.url.path(); 0138 if (parent->hasFavIcon && (path.isNull() || path == QLatin1String("/"))) { 0139 icon = parent->icon; 0140 } else { 0141 icon = QIcon::fromTheme(KProtocolInfo::icon(entry.url.scheme())); 0142 } 0143 } 0144 0145 GroupEntry::GroupEntry(const QUrl &_url, const QString &_key) 0146 : Entry(Group), url(_url), key(_key), hasFavIcon(false) 0147 { 0148 const QString iconPath = KIO::favIconForUrl(url); 0149 if (iconPath.isEmpty()) { 0150 icon = QIcon::fromTheme(QStringLiteral("folder")); 0151 } else { 0152 icon = QIcon(iconPath); 0153 hasFavIcon = true; 0154 } 0155 } 0156 0157 QVariant GroupEntry::data(int role, int /*column*/) const 0158 { 0159 switch (role) { 0160 case Qt::DisplayRole: 0161 return key; 0162 case Qt::DecorationRole: 0163 return icon; 0164 case KonqHistory::TypeRole: 0165 return int(KonqHistory::GroupType); 0166 case KonqHistory::LastVisitedRole: { 0167 if (entries.isEmpty()) { 0168 return QDateTime(); 0169 } 0170 QDateTime dt = entries.first()->entry.lastVisited; 0171 for (HistoryEntry *e: entries) { 0172 if (e->entry.lastVisited > dt) { 0173 dt = e->entry.lastVisited; 0174 } 0175 } 0176 return dt; 0177 } 0178 } 0179 return QVariant(); 0180 } 0181 0182 HistoryEntry *GroupEntry::findChild(const KonqHistoryEntry &entry, int *index) const 0183 { 0184 HistoryEntry *item = nullptr; 0185 QList<HistoryEntry *>::const_iterator it = entries.constBegin(), itEnd = entries.constEnd(); 0186 int i = 0; 0187 for (; it != itEnd; ++it, ++i) { 0188 if ((*it)->entry.url == entry.url) { 0189 item = *it; 0190 break; 0191 } 0192 } 0193 if (index) { 0194 *index = item ? i : -1; 0195 } 0196 return item; 0197 } 0198 0199 QList<QUrl> GroupEntry::urls() const 0200 { 0201 QList<QUrl> list; 0202 for (HistoryEntry *e: entries) { 0203 list.append(e->entry.url); 0204 } 0205 return list; 0206 } 0207 0208 } 0209 0210 static QString groupForUrl(const QUrl &url) 0211 { 0212 if (url.isLocalFile()) { 0213 static const QString &local = i18n("Local"); 0214 return local; 0215 } 0216 static const QString &misc = i18n("Miscellaneous"); 0217 return url.host().isEmpty() ? misc : url.host(); 0218 } 0219 0220 KonqHistoryModel::KonqHistoryModel(QObject *parent) 0221 : QAbstractItemModel(parent), m_root(new KHM::RootEntry()) 0222 { 0223 KonqHistoryProvider *provider = KonqHistoryProvider::self(); 0224 0225 connect(provider, SIGNAL(cleared()), this, SLOT(clear())); 0226 connect(provider, SIGNAL(entryAdded(KonqHistoryEntry)), 0227 this, SLOT(slotEntryAdded(KonqHistoryEntry))); 0228 connect(provider, SIGNAL(entryRemoved(KonqHistoryEntry)), 0229 this, SLOT(slotEntryRemoved(KonqHistoryEntry))); 0230 0231 KonqHistoryList entries(provider->entries()); 0232 0233 KonqHistoryList::const_iterator it = entries.constBegin(); 0234 const KonqHistoryList::const_iterator end = entries.constEnd(); 0235 for (; it != end; ++it) { 0236 KHM::GroupEntry *group = getGroupItem((*it).url, DontEmitSignals); 0237 KHM::HistoryEntry *item = new KHM::HistoryEntry((*it), group); 0238 Q_UNUSED(item); 0239 } 0240 } 0241 0242 KonqHistoryModel::~KonqHistoryModel() 0243 { 0244 delete m_root; 0245 } 0246 0247 int KonqHistoryModel::columnCount(const QModelIndex &parent) const 0248 { 0249 KHM::Entry *entry = entryFromIndex(parent, true); 0250 switch (entry->type) { 0251 case KHM::Entry::History: 0252 return 0; 0253 case KHM::Entry::Group: 0254 case KHM::Entry::Root: 0255 return 1; 0256 } 0257 return 0; 0258 } 0259 0260 QVariant KonqHistoryModel::data(const QModelIndex &index, int role) const 0261 { 0262 KHM::Entry *entry = entryFromIndex(index); 0263 if (!entry) { 0264 return QVariant(); 0265 } 0266 0267 return entry->data(role, index.column()); 0268 } 0269 0270 QModelIndex KonqHistoryModel::index(int row, int column, const QModelIndex &parent) const 0271 { 0272 if (row < 0 || column != 0) { 0273 return QModelIndex(); 0274 } 0275 0276 KHM::Entry *entry = entryFromIndex(parent, true); 0277 switch (entry->type) { 0278 case KHM::Entry::History: 0279 return QModelIndex(); 0280 case KHM::Entry::Group: { 0281 const KHM::GroupEntry *ge = static_cast<KHM::GroupEntry *>(entry); 0282 if (row >= ge->entries.count()) { 0283 return QModelIndex(); 0284 } 0285 return createIndex(row, column, ge->entries.at(row)); 0286 } 0287 case KHM::Entry::Root: { 0288 const KHM::RootEntry *re = static_cast<KHM::RootEntry *>(entry); 0289 if (row >= re->groups.count()) { 0290 return QModelIndex(); 0291 } 0292 return createIndex(row, column, re->groups.at(row)); 0293 } 0294 } 0295 return QModelIndex(); 0296 } 0297 0298 QModelIndex KonqHistoryModel::parent(const QModelIndex &index) const 0299 { 0300 KHM::Entry *entry = entryFromIndex(index); 0301 if (!entry) { 0302 return QModelIndex(); 0303 } 0304 switch (entry->type) { 0305 case KHM::Entry::History: 0306 return indexFor(static_cast<KHM::HistoryEntry *>(entry)->parent); 0307 case KHM::Entry::Group: 0308 case KHM::Entry::Root: 0309 return QModelIndex(); 0310 } 0311 return QModelIndex(); 0312 } 0313 0314 int KonqHistoryModel::rowCount(const QModelIndex &parent) const 0315 { 0316 KHM::Entry *entry = entryFromIndex(parent, true); 0317 switch (entry->type) { 0318 case KHM::Entry::History: 0319 return 0; 0320 case KHM::Entry::Group: 0321 return static_cast<KHM::GroupEntry *>(entry)->entries.count(); 0322 case KHM::Entry::Root: 0323 return static_cast<KHM::RootEntry *>(entry)->groups.count(); 0324 } 0325 return 0; 0326 } 0327 0328 void KonqHistoryModel::deleteItem(const QModelIndex &index) 0329 { 0330 KHM::Entry *entry = entryFromIndex(index); 0331 if (!entry) { 0332 return; 0333 } 0334 0335 KonqHistoryProvider *provider = KonqHistoryProvider::self(); 0336 switch (entry->type) { 0337 case KHM::Entry::History: 0338 provider->emitRemoveFromHistory(static_cast<KHM::HistoryEntry *>(entry)->entry.url); 0339 break; 0340 case KHM::Entry::Group: 0341 provider->emitRemoveListFromHistory(static_cast<KHM::GroupEntry *>(entry)->urls()); 0342 break; 0343 case KHM::Entry::Root: 0344 break; 0345 } 0346 } 0347 0348 void KonqHistoryModel::clear() 0349 { 0350 if (m_root->groups.isEmpty()) { 0351 return; 0352 } 0353 0354 beginResetModel(); 0355 delete m_root; 0356 m_root = new KHM::RootEntry(); 0357 endResetModel(); 0358 } 0359 0360 void KonqHistoryModel::slotEntryAdded(const KonqHistoryEntry &entry) 0361 { 0362 KHM::GroupEntry *group = getGroupItem(entry.url, EmitSignals); 0363 KHM::HistoryEntry *item = group->findChild(entry); 0364 if (!item) { 0365 beginInsertRows(indexFor(group), group->entries.count(), group->entries.count()); 0366 item = new KHM::HistoryEntry(entry, group); 0367 endInsertRows(); 0368 } else { 0369 // Do not update existing entries, otherwise items jump around when clicking on them (#61450) 0370 if (item->entry.lastVisited.isValid()) { 0371 return; 0372 } 0373 item->update(entry); 0374 const QModelIndex index = indexFor(item); 0375 emit dataChanged(index, index); 0376 } 0377 // update the parent item, so the sorting by date is updated accordingly 0378 const QModelIndex groupIndex = indexFor(group); 0379 emit dataChanged(groupIndex, groupIndex); 0380 } 0381 0382 void KonqHistoryModel::slotEntryRemoved(const KonqHistoryEntry &entry) 0383 { 0384 const QString groupKey = groupForUrl(entry.url); 0385 KHM::GroupEntry *group = m_root->groupsByName.value(groupKey); 0386 if (!group) { 0387 return; 0388 } 0389 0390 int index = 0; 0391 KHM::HistoryEntry *item = group->findChild(entry, &index); 0392 if (index == -1) { 0393 return; 0394 } 0395 0396 if (group->entries.count() > 1) { 0397 beginRemoveRows(indexFor(group), index, index); 0398 group->entries.removeAt(index); 0399 delete item; 0400 endRemoveRows(); 0401 } else { 0402 index = m_root->groups.indexOf(group); 0403 if (index == -1) { 0404 return; 0405 } 0406 0407 beginRemoveRows(QModelIndex(), index, index); 0408 m_root->groupsByName.remove(groupKey); 0409 m_root->groups.removeAt(index); 0410 delete group; 0411 endRemoveRows(); 0412 } 0413 } 0414 0415 KHM::Entry *KonqHistoryModel::entryFromIndex(const QModelIndex &index, bool returnRootIfNull) const 0416 { 0417 if (index.isValid()) { 0418 return reinterpret_cast<KHM::Entry *>(index.internalPointer()); 0419 } 0420 return returnRootIfNull ? m_root : nullptr; 0421 } 0422 0423 KHM::GroupEntry *KonqHistoryModel::getGroupItem(const QUrl &url, SignalEmission se) 0424 { 0425 const QString &groupKey = groupForUrl(url); 0426 KHM::GroupEntry *group = m_root->groupsByName.value(groupKey); 0427 if (!group) { 0428 if (se == EmitSignals) { 0429 beginInsertRows(QModelIndex(), m_root->groups.count(), m_root->groups.count()); 0430 } 0431 group = new KHM::GroupEntry(url, groupKey); 0432 m_root->groups.append(group); 0433 m_root->groupsByName.insert(groupKey, group); 0434 if (se == EmitSignals) { 0435 endInsertRows(); 0436 } 0437 } 0438 0439 return group; 0440 } 0441 0442 QModelIndex KonqHistoryModel::indexFor(KHM::HistoryEntry *entry) const 0443 { 0444 const int row = entry->parent->entries.indexOf(entry); 0445 if (row < 0) { 0446 return QModelIndex(); 0447 } 0448 return createIndex(row, 0, entry); 0449 } 0450 0451 QModelIndex KonqHistoryModel::indexFor(KHM::GroupEntry *entry) const 0452 { 0453 const int row = m_root->groups.indexOf(entry); 0454 if (row < 0) { 0455 return QModelIndex(); 0456 } 0457 return createIndex(row, 0, entry); 0458 } 0459