/* Copyright 2007 Robert Knight Copyright 2011 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Own #include "recentlyusedmodel.h" // Qt // KDE #include #include #include #include #include // Local #include "recentapplications.h" #include "recentadaptor.h" using namespace Kickoff; class RecentlyUsedModel::Private { public: Private(RecentlyUsedModel *parent, RecentType recenttype, int maxRecentApps) : q(parent), recenttype(recenttype), maxRecentApps(maxRecentApps >= 0 ? maxRecentApps : Kickoff::RecentApplications::self()->defaultMaximum()), displayOrder(NameAfterDescription), applicationTitle(i18n("Applications")), documentTitle(i18n("Documents")), recentApplicationCount(0) { } bool removeExistingItem(const QString& path) { if (!itemsByPath.contains(path)) { return false; } QStandardItem *existingItem = itemsByPath[path]; qDebug() << "Removing existing item" << existingItem; QModelIndex index = q->indexFromItem(existingItem); if (!index.isValid()) { qDebug() << "Now Index for our existing item"; return false; } q->takeRow(index.row()); itemsByPath.remove(path); delete existingItem; return true; } void addRecentApplication(KService::Ptr service, bool append) { // remove existing item if any if (removeExistingItem(service->entryPath())) { --recentApplicationCount; } QStandardItem *appItem = StandardItemFactory::createItemForService(service, displayOrder); appItem->setData(applicationTitle, Kickoff::GroupNameRole); itemsByPath.insert(service->entryPath(), appItem); if (append) { q->insertRow(recentApplicationCount, appItem); } else { q->insertRow(0, appItem); } ++recentApplicationCount; while (recentApplicationCount > maxRecentApps) { --recentApplicationCount; QList row = q->takeRow(recentApplicationCount); //don't leave pending stuff in itemsByPath if (!row.isEmpty()) { itemsByPath.remove(row.first()->data(UrlRole).toString()); qDeleteAll(row.begin(), row.end()); } } } void addRecentDocument(const QString& desktopPath, bool append) { // remove existing item if any removeExistingItem(desktopPath); QStandardItem *documentItem = StandardItemFactory::createItemForUrl(desktopPath, displayOrder); documentItem->setData(true, Kickoff::SubTitleMandatoryRole); documentItem->setData(documentTitle, Kickoff::GroupNameRole); itemsByPath.insert(desktopPath, documentItem); if (append) { q->appendRow(documentItem); } else { q->insertRow(recentApplicationCount, documentItem); } } void loadRecentDocuments() { // create branch for documents and add existing items const QStringList documents = KRecentDocument::recentDocuments(); foreach(const QString& document, documents) { addRecentDocument(document, true); } } void loadRecentApplications() { const QList services = RecentApplications::self()->recentApplications(); for(int i = 0; i < maxRecentApps && i < services.count(); ++i) { addRecentApplication(services[i], true); } } RecentlyUsedModel * const q; RecentType recenttype; int maxRecentApps; QHash itemsByPath; DisplayOrder displayOrder; QString applicationTitle; QString documentTitle; int recentApplicationCount; }; RecentlyUsedModel::RecentlyUsedModel(QObject *parent, RecentType recenttype, int maxRecentApps) : KickoffModel(parent), d(new Private(this, recenttype, maxRecentApps)) { QDBusConnection dbus = QDBusConnection::sessionBus(); (void)new RecentAdaptor(this); QDBusConnection::sessionBus().registerObject("/kickoff/RecentAppDoc", this); dbus.connect(QString(), "/kickoff/RecentAppDoc", "org.kde.plasma", "clearRecentDocumentsAndApplications", this, SLOT(clearRecentDocumentsAndApplications())); if (recenttype != DocumentsOnly) { d->loadRecentApplications(); // listen for changes to the list of recent applications connect(RecentApplications::self(), &RecentApplications::applicationAdded, this, &RecentlyUsedModel::recentApplicationAdded); connect(RecentApplications::self(), &RecentApplications::applicationRemoved, this, &RecentlyUsedModel::recentApplicationRemoved); connect(RecentApplications::self(), &RecentApplications::cleared, this, &RecentlyUsedModel::recentApplicationsCleared); } if (recenttype != ApplicationsOnly) { d->loadRecentDocuments(); // listen for changes to the list of recent documents KDirWatch *recentDocWatch = new KDirWatch(this); recentDocWatch->addDir(KRecentDocument::recentDocumentDirectory(), KDirWatch::WatchFiles); connect(recentDocWatch, &KDirWatch::created, this, &RecentlyUsedModel::recentDocumentAdded); connect(recentDocWatch, &KDirWatch::deleted, this, &RecentlyUsedModel::recentDocumentRemoved); connect(recentDocWatch, &KDirWatch::dirty, this, &RecentlyUsedModel::recentDocumentChanged); } } RecentlyUsedModel::~RecentlyUsedModel() { delete d; } QVariant RecentlyUsedModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal || section != 0) { return QVariant(); } switch (role) { case Qt::DisplayRole: if (d->recenttype == DocumentsAndApplications) { return i18n("Recently Used"); } else if (d->recenttype == DocumentsOnly) { return i18n("Recently Used Documents"); } else if (d->recenttype == ApplicationsOnly) { return i18n("Recently Used Applications"); } default: return QVariant(); } } void RecentlyUsedModel::setNameDisplayOrder(DisplayOrder displayOrder) { if (d->displayOrder == displayOrder) { return; } d->displayOrder = displayOrder; d->itemsByPath.clear(); clear(); d->recentApplicationCount = 0; if (d->recenttype != DocumentsOnly) { d->loadRecentApplications(); } if (d->recenttype != ApplicationsOnly) { d->loadRecentDocuments(); } } DisplayOrder RecentlyUsedModel::nameDisplayOrder() const { return d->displayOrder; } void RecentlyUsedModel::recentDocumentAdded(const QString& path) { qDebug() << "Recent document added" << path; d->addRecentDocument(path, false); } void RecentlyUsedModel::recentDocumentRemoved(const QString& path) { qDebug() << "Recent document removed" << path; d->removeExistingItem(path); } void RecentlyUsedModel::recentDocumentChanged(const QString& path) { // filter out the noise from parent dir/temp files if (!d->itemsByPath.contains(path)) { return; } qDebug() << "Recent document changed" << path; d->addRecentDocument(path, false); } void RecentlyUsedModel::recentApplicationAdded(KService::Ptr service, int) { if (service) { d->addRecentApplication(service, false); } } void RecentlyUsedModel::recentApplicationRemoved(KService::Ptr service) { if (service) { if (d->removeExistingItem(service->entryPath())) --d->recentApplicationCount; } } void RecentlyUsedModel::recentApplicationsCleared() { while (d->recentApplicationCount != 0) { QListitems = takeRow(0); Q_ASSERT(items.count() == 1); //model is only 1 level deep, so this will only ever remove one d->itemsByPath.remove(items.first()->data(UrlRole).toString()); delete items[0]; --d->recentApplicationCount; } } void RecentlyUsedModel::clearRecentApplications() { RecentApplications::self()->clear(); } void RecentlyUsedModel::clearRecentDocuments() { KRecentDocument::clear(); } void RecentlyUsedModel::clearRecentDocumentsAndApplications() { clearRecentDocuments(); clearRecentApplications(); }