4
* Copyright 2011 Craig Drummond <craig@kde.org>
8
* This program is free software; you can redistribute it and/or modify
9
* it under the terms of the GNU General Public License as published by
10
* the Free Software Foundation; either version 2 of the License, or
11
* (at your option) any later version.
13
* This program is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
* General Public License for more details.
18
* You should have received a copy of the GNU General Public License
19
* along with this program; see the file COPYING. If not, write to
20
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21
* Boston, MA 02110-1301, USA.
24
#include "recentdocuments.h"
25
#include <KDE/KRecentDocument>
26
#include <KDE/KDirWatch>
27
#include <KDE/KGlobal>
28
#include <KDE/KDesktopFile>
29
#include <KDE/KConfigGroup>
32
#include <KDE/KStandardDirs>
33
#include <KDE/KSycoca>
35
#include <KDE/KServiceTypeTrader>
36
#include <KDE/KService>
37
#include <KDE/KMimeType>
38
#include <QtXml/QDomDocument>
39
#include <QtXml/QDomElement>
40
#include <QtXml/QDomNode>
41
#include <QtXml/QDomText>
42
#include <QtCore/QDir>
43
#include <QtCore/QFile>
44
#include <QtCore/QDateTime>
46
K_GLOBAL_STATIC(RecentDocuments, recentDocs)
48
static QLatin1String constXbel("recently-used.xbel");
50
static QList<QAction *>::ConstIterator findUrl(const QList<QAction *> &list, const QString &url)
52
QList<QAction *>::ConstIterator it(list.constBegin()),
54
for (; it != end; ++it) {
55
if ((*it)->property("url") == url) {
62
static bool hasUrl(const QList<QAction *> &list, const QString &url)
64
return list.end() != findUrl(list, url);
67
static QString dirSyntax(const QString &d)
72
ds.replace("//", "/");
74
int slashPos(ds.lastIndexOf('/'));
76
if (slashPos != (((int)ds.length()) - 1))
85
RecentDocuments * RecentDocuments::self()
90
RecentDocuments::RecentDocuments()
97
RecentDocuments::~RecentDocuments()
100
m_menu->deleteLater();
104
void RecentDocuments::setEnabled(bool enabled)
106
if (m_enabled != enabled) {
108
if (m_files.isEmpty()) {
109
m_files << File(File::Xbel, dirSyntax(KGlobal::dirs()->localxdgdatadir()) + constXbel)
110
<< File(File::Xbel, dirSyntax(QDir::homePath()) + "." + constXbel)
111
<< File(File::Office, dirSyntax(QDir::homePath()) + ".recently-used");
114
m_watcher = new KDirWatch(this);
115
m_watcher->addDir(KRecentDocument::recentDocumentDirectory(), KDirWatch::WatchFiles);
116
foreach (File f, m_files) {
117
m_watcher->addFile(f.path);
119
connect(m_watcher, SIGNAL(created(QString)), this, SLOT(added(QString)));
120
connect(m_watcher, SIGNAL(deleted(QString)), this, SLOT(removed(QString)));
121
connect(m_watcher, SIGNAL(dirty(QString)), this, SLOT(modified(QString)));
122
connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), this, SLOT(sycocaChanged(const QStringList &)));
124
} else if (m_enabled) {
125
disconnect(m_watcher, SIGNAL(created(QString)), this, SLOT(added(QString)));
126
disconnect(m_watcher, SIGNAL(deleted(QString)), this, SLOT(removed(QString)));
127
disconnect(m_watcher, SIGNAL(dirty(QString)), this, SLOT(modified(QString)));
128
disconnect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), this, SLOT(sycocaChanged(const QStringList &)));
132
QMap<QString, QList<QAction *> >::Iterator it(m_docs.begin()),
135
for (; it != end; ++it) {
136
foreach (const QAction * act, *it) {
147
QList<QAction *> RecentDocuments::get(const QString &app)
151
if (m_docs.contains(app)) {
152
if (m_docs[app].count() > 1) {
154
m_menu = new TaskManager::ToolTipMenu(0, i18n("Recent Documents"));
157
QList<QAction *> old=m_menu->actions();
158
foreach (QAction * act, old) {
159
m_menu->removeAction(act);
162
foreach (QAction * act, m_docs[app]) {
163
m_menu->addAction(act);
166
QList<QAction *> acts;
167
acts.append(m_menu->menuAction());
173
return QList<QAction *>();
176
void RecentDocuments::added(const QString &path)
178
if (KDesktopFile::isDesktopFile(path)) {
179
removed(path); // Remove first!
180
KDesktopFile df(path);
181
KConfigGroup de(&df, "Desktop Entry");
182
QString url = de.readEntry("URL", QString());
183
QString name = KUrl(url).fileName();
184
QString app = de.readEntry("X-KDE-LastOpenedWith", QString());
186
if (!name.isEmpty() && !app.isEmpty() && !url.isEmpty() && !hasUrl(m_docs[app], url)) {
187
QString icon = de.readEntry("Icon", QString());
188
QAction *act = icon.isEmpty() ? new QAction(name, this) : new QAction(KIcon(icon), name, this);
189
act->setToolTip(KUrl(url).prettyUrl());
190
act->setProperty("timestamp", (qulonglong)0);
191
act->setProperty("path", path);
192
act->setProperty("url", url);
193
connect(act, SIGNAL(triggered()), SLOT(loadDoc()));
194
m_docs[app].append(act);
197
QList<File>::Iterator it(m_files.begin()),
199
for (; it != end; ++it) {
200
if ((*it).path == path) {
208
void RecentDocuments::removed(const QString &path)
210
if (path.endsWith(".desktop")) {
211
QMap<QString, QList<QAction *> >::Iterator it(m_docs.begin()),
214
for (; it != end; ++it) {
215
foreach (QAction * act, *it) {
216
if (act->property("path").toString() == path) {
217
disconnect(act, SIGNAL(triggered()), this, SLOT(loadDoc()));
219
(*it).removeAll(act);
220
if ((*it).isEmpty()) {
228
QList<File>::Iterator it(m_files.begin()),
230
for (; it != end; ++it) {
231
if ((*it).path == path) {
239
void RecentDocuments::modified(const QString &path)
241
QList<File>::Iterator it(m_files.begin()),
243
for (; it != end; ++it) {
244
if ((*it).path == path) {
251
void RecentDocuments::sycocaChanged(const QStringList &types)
253
if (types.contains("apps")) {
255
QList<File>::Iterator it(m_files.begin()),
257
for (; it != end; ++it) {
258
if (File::Xbel == (*it).type) {
265
void RecentDocuments::loadDoc()
267
QObject *s = sender();
268
if (s && qobject_cast<QAction *>(s)) {
269
QAction *item = static_cast<QAction *>(s);
270
QString path = item->property("path").toString();
272
if (path.isEmpty()) {
273
QString exec = item->property("exec").toString();
274
KUrl url = KUrl(item->property("url").toString());
276
if (url.isValid() && !exec.isEmpty()) {
277
KRun::run(exec, KUrl::List() << url, 0, QString(), QString(), "0");
280
new KRun(KUrl(path), 0);
285
void RecentDocuments::readCurrentDocs()
287
const QStringList documents = KRecentDocument::recentDocuments();
288
foreach (const QString & document, documents) {
293
void RecentDocuments::load()
295
qulonglong now = (qulonglong)QDateTime::currentMSecsSinceEpoch();
296
QList<File>::Iterator it(m_files.begin()),
298
for (; it != end; ++it) {
300
if (File::Xbel == (*it).type) {
301
loadXbel((*it).path, now);
302
} else if (File::Office == (*it).type) {
303
loadOffice((*it).path, now);
310
static QString convertMimeType(const QString &mimeType, const KUrl &url)
312
return mimeType == "text/plain" && url.fileName().endsWith(".csv")
313
? QLatin1String("text/csv") : mimeType;
316
RecentDocuments::App RecentDocuments::officeAppForMimeType(const QString &mimeType)
318
if (m_apps.contains(mimeType)) {
319
return m_apps[mimeType];
321
KService::List services = KServiceTypeTrader::self()->query("Application",
322
QString("exist Exec and (exist ServiceTypes) and ('libreoffice' ~ Exec) and ('%1' in ServiceTypes)").arg(mimeType));
324
if (!services.empty()) {
325
QString desktopFile = services[0]->entryPath();
326
KDesktopFile df(desktopFile);
327
KConfigGroup grp(&df, "Desktop Entry");
328
QString exec = grp.readEntry("Exec", QString());
330
if (!exec.isEmpty()) {
331
App app(KUrl::fromPath(desktopFile).fileName().remove(".desktop"), exec);
332
m_apps.insert(mimeType, app);
341
RecentDocuments::App RecentDocuments::appForExec(const QString &execString)
343
if (m_apps.contains(execString)) {
344
return m_apps[execString];
346
KService::List services = KServiceTypeTrader::self()->query("Application",
347
QString("exist Exec and ('%1' =~ Exec)").arg(execString));
348
if (services.empty()) {
349
QString execApp = execString;
350
int space = execApp.indexOf(' ');
352
execApp = execApp.left(space);
354
services = KServiceTypeTrader::self()->query("Application",
355
QString("exist TryExec and ('%1' =~ TryExec)").arg(execApp));
357
if (!services.empty()) {
358
QString desktopFile = services[0]->entryPath();
359
KDesktopFile df(desktopFile);
360
KConfigGroup grp(&df, "Desktop Entry");
361
QString exec = grp.readEntry("Exec", QString());
363
if (!exec.isEmpty()) {
364
App app(KUrl::fromPath(desktopFile).fileName().remove(".desktop"), exec);
365
m_apps.insert(execString, app);
374
void RecentDocuments::loadXbel(const QString &path, qulonglong now)
376
QDomDocument doc("xbel");
379
if (f.open(QIODevice::ReadOnly) && doc.setContent(&f)) {
380
QDomElement root = doc.documentElement();
381
if ("xbel" == root.tagName() && root.hasAttribute("version") && "1.0" == root.attribute("version")) {
382
QDomElement bookmark = root.firstChildElement("bookmark");
383
while (!bookmark.isNull()) {
384
if (bookmark.hasAttribute("href")) {
385
QDomElement info = bookmark.firstChildElement("info");
386
if (!info.isNull()) {
387
QDomElement metadata = info.firstChildElement("metadata");
388
if (!metadata.isNull() && metadata.hasAttribute("owner") && "http://freedesktop.org" == metadata.attribute("owner")) {
389
QDomElement applications = metadata.firstChildElement("bookmark:applications");
390
if (!applications.isNull()) {
391
QDomElement application = applications.firstChildElement("bookmark:application");
392
if (!application.isNull() && application.hasAttribute("exec")) {
393
KUrl url = bookmark.attribute("href");
394
if (url.isValid() && (!url.isLocalFile() || QFile::exists(url.toLocalFile()))) {
395
QString exec = application.attribute("exec");
396
QDomElement mimeType = metadata.firstChildElement("mime:mime-type");
399
if (!mimeType.isNull() && mimeType.hasAttribute("type")) {
400
mType = convertMimeType(mimeType.attribute("type"), url);
401
mime = KMimeType::mimeType(mType);
406
App app = mime && QLatin1String("soffice %u")==exec
407
? officeAppForMimeType(mType)
410
if (!app.name.isEmpty()) {
411
QString name = KUrl(url).fileName();
413
if (!name.isEmpty()) {
415
if (!m_docs[app.name].isEmpty()) {
416
QList<QAction *>::ConstIterator it = findUrl(m_docs[app.name], url.url());
417
if (it != m_docs[app.name].constEnd()) {
419
if ((*it)->property("timestamp").toULongLong() > 0) {
420
(*it)->setProperty("timestamp", now);
426
? new QAction(KIcon(mime->iconName()), name, this)
427
: new QAction(name, this);
429
act->setToolTip(KUrl(url).prettyUrl());
430
act->setProperty("timestamp", now);
431
act->setProperty("url", url.url());
432
act->setProperty("exec", app.exec);
433
act->setProperty("type", (int)File::Xbel);
434
connect(act, SIGNAL(triggered()), SLOT(loadDoc()));
435
m_docs[app.name].append(act);
445
bookmark = bookmark.nextSiblingElement("bookmark");
450
removeOld(now, File::Xbel);
453
void RecentDocuments::loadOffice(const QString &path, qulonglong now)
455
QDomDocument doc("RecentFiles");
458
if (f.open(QIODevice::ReadOnly) && doc.setContent(&f)) {
459
QDomElement root = doc.documentElement();
460
if ("RecentFiles" == root.tagName()) {
461
QDomElement recentItem = root.firstChildElement("RecentItem");
462
while (!recentItem.isNull()) {
463
QDomElement groups = recentItem.firstChildElement("Groups");
464
if (!groups.isNull()) {
465
QDomElement group = groups.firstChildElement("Group");
467
while (!group.isNull()) {
468
if (group.text() == "openoffice.org") {
472
group = group.nextSiblingElement("Group");
476
QDomElement uri = recentItem.firstChildElement("URI");
477
QDomElement mimeType = recentItem.firstChildElement("Mime-Type");
479
if (!uri.isNull() && !mimeType.isNull()) {
480
KUrl url(uri.text());
482
if (url.isValid() && (!url.isLocalFile() || QFile::exists(url.toLocalFile()))) {
483
QString mType = convertMimeType(mimeType.text(), url);
484
App app = officeAppForMimeType(mType);
486
if (!app.name.isEmpty() && !app.exec.isEmpty()) {
487
QString name = KUrl(url).fileName();
489
if (!name.isEmpty()) {
491
if (!m_docs[app.name].isEmpty()) {
492
QList<QAction *>::ConstIterator it = findUrl(m_docs[app.name], url.url());
493
if (it != m_docs[app.name].constEnd()) {
495
if ((*it)->property("timestamp").toULongLong() > 0) {
496
(*it)->setProperty("timestamp", now);
501
KMimeType::Ptr mime = KMimeType::mimeType(mType);
503
? new QAction(KIcon(mime->iconName()), name, this)
504
: new QAction(name, this);
506
act->setToolTip(KUrl(url).prettyUrl());
507
act->setProperty("local", false);
508
act->setProperty("timestamp", now);
509
act->setProperty("url", url.url());
510
act->setProperty("exec", app.exec);
511
act->setProperty("type", (int)File::Office);
512
connect(act, SIGNAL(triggered()), SLOT(loadDoc()));
513
m_docs[app.name].append(act);
521
recentItem = recentItem.nextSiblingElement("RecentItem");
526
removeOld(now, File::Office);
529
void RecentDocuments::removeOld(qulonglong now, File::Type type)
531
QMap<QString, QList<QAction *> >::Iterator it(m_docs.begin()),
534
QList<QAction *> old;
536
foreach (QAction * act, (*it)) {
537
qulonglong t = act->property("timestamp").toULongLong();
538
if (type==act->property("type").toInt() && t > 0 && t < now) {
543
foreach (QAction * act, old) {
545
(*it).removeAll(act);
548
if ((*it).isEmpty()) {
549
QMap<QString, QList<QAction *> >::Iterator cur = it;
558
#include "recentdocuments.moc"