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