File indexing completed on 2024-05-05 16:08:25

0001 /*****************************************************************************
0002  * Copyright (C) 2010 by Peter Penz <peter.penz@gmx.at>                      *
0003  *                                                                           *
0004  * This library is free software; you can redistribute it and/or             *
0005  * modify it under the terms of the GNU Library General Public               *
0006  * License as published by the Free Software Foundation; either              *
0007  * version 2 of the License, or (at your option) any later version.          *
0008  *                                                                           *
0009  * This library is distributed in the hope that it will be useful,           *
0010  * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
0011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU         *
0012  * Library General Public License for more details.                          *
0013  *                                                                           *
0014  * You should have received a copy of the GNU Library General Public License *
0015  * along with this library; see the file COPYING.LIB.  If not, write to      *
0016  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,      *
0017  * Boston, MA 02110-1301, USA.                                               *
0018  *****************************************************************************/
0019 
0020 #include "kfilemetadataprovider_p.h"
0021 
0022 #include <kfileitem.h>
0023 #include <kfilemetadatareader_p.h>
0024 #include "knfotranslator_p.h"
0025 
0026 #if ! KIO_NO_NEPOMUK
0027 #define DISABLE_NEPOMUK_LEGACY
0028 #include "nepomukmassupdatejob.h"
0029 #include "tagwidget.h"
0030 #include "tag.h"
0031 #include "kratingwidget.h"
0032 #include "resource.h"
0033 #include "resourcemanager.h"
0034 
0035 #include "kcommentwidget_p.h"
0036 #else
0037 namespace Nepomuk
0038 {
0039 typedef int Tag;
0040 }
0041 #endif
0042 
0043 #include <QEvent>
0044 #include <QLabel>
0045 #include <QLocale>
0046 #include <QUrl>
0047 
0048 // Required includes for subDirectoriesCount():
0049 #ifdef Q_OS_WIN
0050 #include <QDir>
0051 #else
0052 #include <dirent.h>
0053 #include <QFile>
0054 #endif
0055 
0056 namespace
0057 {
0058 static QString plainText(const QString &richText)
0059 {
0060     QString plainText;
0061     plainText.reserve(richText.length());
0062 
0063     bool skip = false;
0064     for (int i = 0; i < richText.length(); ++i) {
0065         const QChar c = richText.at(i);
0066         if (c == QLatin1Char('<')) {
0067             skip = true;
0068         } else if (c == QLatin1Char('>')) {
0069             skip = false;
0070         } else if (!skip) {
0071             plainText.append(c);
0072         }
0073     }
0074 
0075     return plainText;
0076 }
0077 }
0078 
0079 // The default size hint of QLabel tries to return a square size.
0080 // This does not work well in combination with layouts that use
0081 // heightForWidth(): In this case it is possible that the content
0082 // of a label might get clipped. By specifying a size hint
0083 // with a maximum width that is necessary to contain the whole text,
0084 // using heightForWidth() assures having a non-clipped text.
0085 class ValueWidget : public QLabel
0086 {
0087 public:
0088     explicit ValueWidget(QWidget *parent = 0);
0089     virtual QSize sizeHint() const;
0090 };
0091 
0092 ValueWidget::ValueWidget(QWidget *parent) :
0093     QLabel(parent)
0094 {
0095 }
0096 
0097 QSize ValueWidget::sizeHint() const
0098 {
0099     QFontMetrics metrics(font());
0100     // TODO: QLabel internally provides already a method sizeForWidth(),
0101     // that would be sufficient. However this method is not accessible, so
0102     // as workaround the tags from a richtext are removed manually here to
0103     // have a proper size hint.
0104     return metrics.size(Qt::TextSingleLine, plainText(text()));
0105 }
0106 
0107 class KFileMetaDataProvider::Private
0108 {
0109 
0110 public:
0111     Private(KFileMetaDataProvider *parent);
0112     ~Private();
0113 
0114     void slotLoadingFinished();
0115 
0116     void slotRatingChanged(unsigned int rating);
0117     void slotTagsChanged(const QList<Nepomuk::Tag> &tags);
0118     void slotCommentChanged(const QString &comment);
0119 
0120     void slotMetaDataUpdateDone();
0121     void slotTagClicked(const Nepomuk::Tag &tag);
0122     void slotLinkActivated(const QString &link);
0123 
0124     /**
0125      * Disables the metadata widget and starts the job that
0126      * changes the meta data asynchronously. After the job
0127      * has been finished, the metadata widget gets enabled again.
0128      */
0129     void startChangeDataJob(KJob *job);
0130 
0131 #if ! KIO_NO_NEPOMUK
0132     QList<Nepomuk::Resource> resourceList() const;
0133     QWidget *createRatingWidget(int rating, QWidget *parent);
0134     QWidget *createTagWidget(const QList<Nepomuk::Tag> &tags, QWidget *parent);
0135     QWidget *createCommentWidget(const QString &comment, QWidget *parent);
0136 #endif
0137     QWidget *createValueWidget(const QString &value, QWidget *parent);
0138 
0139     /*
0140      * @return The number of subdirectories for the directory \a path.
0141      */
0142     static int subDirectoriesCount(const QString &path);
0143 
0144     bool m_readOnly;
0145     bool m_nepomukActivated;
0146     QList<KFileItem> m_fileItems;
0147 
0148 #if ! KIO_NO_NEPOMUK
0149     QHash<QUrl, Nepomuk::Variant> m_data;
0150 
0151     QList<KFileMetaDataReader *> m_metaDataReaders;
0152     KFileMetaDataReader *m_latestMetaDataReader;
0153 
0154     QPointer<KRatingWidget> m_ratingWidget;
0155     QPointer<Nepomuk::TagWidget> m_tagWidget;
0156     QPointer<KCommentWidget> m_commentWidget;
0157 #endif
0158 
0159 private:
0160     KFileMetaDataProvider *const q;
0161 };
0162 
0163 KFileMetaDataProvider::Private::Private(KFileMetaDataProvider *parent) :
0164     m_readOnly(false),
0165     m_nepomukActivated(false),
0166     m_fileItems(),
0167 #if ! KIO_NO_NEPOMUK
0168     m_data(),
0169     m_metaDataReaders(),
0170     m_latestMetaDataReader(0),
0171     m_ratingWidget(),
0172     m_tagWidget(),
0173     m_commentWidget(),
0174 #endif
0175     q(parent)
0176 {
0177 #if ! KIO_NO_NEPOMUK
0178     m_nepomukActivated = Nepomuk::ResourceManager::instance()->initialized();
0179 #endif
0180 }
0181 
0182 KFileMetaDataProvider::Private::~Private()
0183 {
0184 #if ! KIO_NO_NEPOMUK
0185     qDeleteAll(m_metaDataReaders);
0186 #endif
0187 }
0188 
0189 QString _k_fancyFormatDateTime(const QDateTime &dateTime)
0190 {
0191     QString dateStr;
0192 
0193     // Only do Fancy if less than an hour into the future or less than a week in the past
0194     if ((daysTo == 0 && secsTo > 3600) ||  daysTo < 0 || daysTo > 6) {
0195         dateStr = dateTime.date().toString(Qt::DefaultLocaleLongDate);
0196     } else {
0197         switch (daysTo) {
0198         case 0:
0199             dateStr = i18n("Today");
0200             break;
0201         case 1:
0202             dateStr = i18n("Yesterday");
0203             break;
0204         default:
0205             dateStr = QLocale().dayName(dateTime.date().dayOfWeek());
0206         }
0207     }
0208 
0209     return i18nc("concatenation of dates and time", "%1 %2", dateStr,
0210                  dateTime.time().toString(Qt::DefaultLocaleShortDate));
0211 }
0212 
0213 void KFileMetaDataProvider::Private::slotLoadingFinished()
0214 {
0215 #if ! KIO_NO_NEPOMUK
0216     KFileMetaDataReader *finishedMetaDataReader = qobject_cast<KFileMetaDataReader *>(q->sender());
0217     // The process that has emitted the finished() signal
0218     // will get deleted and removed from m_metaDataReaders.
0219     for (int i = 0; i < m_metaDataReaders.count(); ++i) {
0220         KFileMetaDataReader *metaDataReader = m_metaDataReaders[i];
0221         if (metaDataReader == finishedMetaDataReader) {
0222             m_metaDataReaders.removeAt(i);
0223             if (metaDataReader != m_latestMetaDataReader) {
0224                 // Ignore data of older processs, as the data got
0225                 // obsolete by m_latestMetaDataReader.
0226                 metaDataReader->deleteLater();
0227                 return;
0228             }
0229         }
0230     }
0231 
0232     m_data = m_latestMetaDataReader->metaData();
0233     m_latestMetaDataReader->deleteLater();
0234 
0235     if (m_fileItems.count() == 1) {
0236         // TODO: Handle case if remote URLs are used properly. isDir() does
0237         // not work, the modification date needs also to be adjusted...
0238         const KFileItem &item = m_fileItems.first();
0239 
0240         if (item.isDir()) {
0241             const int count = subDirectoriesCount(item.url().pathOrUrl());
0242             if (count == -1) {
0243                 m_data.insert(QUrl("kfileitem#size"), QString("Unknown"));
0244             } else {
0245                 const QString itemCountString = i18ncp("@item:intable", "%1 item", "%1 items", count);
0246                 m_data.insert(QUrl("kfileitem#size"), itemCountString);
0247             }
0248         } else {
0249             m_data.insert(QUrl("kfileitem#size"), KIO::convertSize(item.size()));
0250         }
0251         m_data.insert(QUrl("kfileitem#type"), item.mimeComment());
0252         m_data.insert(QUrl("kfileitem#modified"), _k_fancyFormatDateTime(item.time(KFileItem::ModificationTime)));
0253         m_data.insert(QUrl("kfileitem#owner"), item.user());
0254         m_data.insert(QUrl("kfileitem#permissions"), item.permissionsString());
0255     } else if (m_fileItems.count() > 1) {
0256         // Calculate the size of all items
0257         quint64 totalSize = 0;
0258         foreach (const KFileItem &item, m_fileItems) {
0259             if (!item.isDir() && !item.isLink()) {
0260                 totalSize += item.size();
0261             }
0262         }
0263         m_data.insert(QUrl("kfileitem#totalSize"), KIO::convertSize(totalSize));
0264     }
0265 #endif
0266 
0267     emit q->loadingFinished();
0268 }
0269 
0270 void KFileMetaDataProvider::Private::slotRatingChanged(unsigned int rating)
0271 {
0272 #if ! KIO_NO_NEPOMUK
0273     Nepomuk::MassUpdateJob *job = Nepomuk::MassUpdateJob::rateResources(resourceList(), rating);
0274     startChangeDataJob(job);
0275 #else
0276     Q_UNUSED(rating);
0277 #endif
0278 }
0279 
0280 void KFileMetaDataProvider::Private::slotTagsChanged(const QList<Nepomuk::Tag> &tags)
0281 {
0282 #if ! KIO_NO_NEPOMUK
0283     if (!m_tagWidget.isNull()) {
0284         m_tagWidget.data()->setSelectedTags(tags);
0285 
0286         Nepomuk::MassUpdateJob *job = Nepomuk::MassUpdateJob::tagResources(resourceList(), tags);
0287         startChangeDataJob(job);
0288     }
0289 #else
0290     Q_UNUSED(tags);
0291 #endif
0292 }
0293 
0294 void KFileMetaDataProvider::Private::slotCommentChanged(const QString &comment)
0295 {
0296 #if ! KIO_NO_NEPOMUK
0297     Nepomuk::MassUpdateJob *job = Nepomuk::MassUpdateJob::commentResources(resourceList(), comment);
0298     startChangeDataJob(job);
0299 #else
0300     Q_UNUSED(comment);
0301 #endif
0302 }
0303 
0304 void KFileMetaDataProvider::Private::slotTagClicked(const Nepomuk::Tag &tag)
0305 {
0306 #if ! KIO_NO_NEPOMUK
0307     emit q->urlActivated(tag.resourceUri());
0308 #else
0309     Q_UNUSED(tag);
0310 #endif
0311 }
0312 
0313 void KFileMetaDataProvider::Private::slotLinkActivated(const QString &link)
0314 {
0315     emit q->urlActivated(QUrl(link));
0316 }
0317 
0318 void KFileMetaDataProvider::Private::startChangeDataJob(KJob *job)
0319 {
0320     connect(job, SIGNAL(result(KJob*)),
0321             q, SIGNAL(dataChangeFinished()));
0322     emit q->dataChangeStarted();
0323     job->start();
0324 }
0325 
0326 #if ! KIO_NO_NEPOMUK
0327 QList<Nepomuk::Resource> KFileMetaDataProvider::Private::resourceList() const
0328 {
0329     QList<Nepomuk::Resource> list;
0330     foreach (const KFileItem &item, m_fileItems) {
0331         const QUrl url = item.nepomukUri();
0332         if (url.isValid()) {
0333             list.append(Nepomuk::Resource(url));
0334         }
0335     }
0336     return list;
0337 }
0338 
0339 QWidget *KFileMetaDataProvider::Private::createRatingWidget(int rating, QWidget *parent)
0340 {
0341     KRatingWidget *ratingWidget = new KRatingWidget(parent);
0342     const Qt::Alignment align = (ratingWidget->layoutDirection() == Qt::LeftToRight) ?
0343                                 Qt::AlignLeft : Qt::AlignRight;
0344     ratingWidget->setAlignment(align);
0345     ratingWidget->setRating(rating);
0346     const QFontMetrics metrics(parent->font());
0347     ratingWidget->setPixmapSize(metrics.height());
0348 
0349     connect(ratingWidget, SIGNAL(ratingChanged(uint)),
0350             q, SLOT(slotRatingChanged(uint)));
0351 
0352     m_ratingWidget = ratingWidget;
0353 
0354     return ratingWidget;
0355 }
0356 
0357 QWidget *KFileMetaDataProvider::Private::createTagWidget(const QList<Nepomuk::Tag> &tags, QWidget *parent)
0358 {
0359     Nepomuk::TagWidget *tagWidget = new Nepomuk::TagWidget(parent);
0360     tagWidget->setModeFlags(m_readOnly
0361                             ? Nepomuk::TagWidget::MiniMode | Nepomuk::TagWidget::ReadOnly
0362                             : Nepomuk::TagWidget::MiniMode);
0363     tagWidget->setSelectedTags(tags);
0364 
0365     connect(tagWidget, SIGNAL(selectionChanged(QList<Nepomuk::Tag>)),
0366             q, SLOT(slotTagsChanged(QList<Nepomuk::Tag>)));
0367     connect(tagWidget, SIGNAL(tagClicked(Nepomuk::Tag)),
0368             q, SLOT(slotTagClicked(Nepomuk::Tag)));
0369 
0370     m_tagWidget = tagWidget;
0371 
0372     return tagWidget;
0373 }
0374 
0375 QWidget *KFileMetaDataProvider::Private::createCommentWidget(const QString &comment, QWidget *parent)
0376 {
0377     KCommentWidget *commentWidget = new KCommentWidget(parent);
0378     commentWidget->setText(comment);
0379     commentWidget->setReadOnly(m_readOnly);
0380 
0381     connect(commentWidget, SIGNAL(commentChanged(QString)),
0382             q, SLOT(slotCommentChanged(QString)));
0383 
0384     m_commentWidget = commentWidget;
0385 
0386     return commentWidget;
0387 }
0388 #endif
0389 
0390 QWidget *KFileMetaDataProvider::Private::createValueWidget(const QString &value, QWidget *parent)
0391 {
0392     ValueWidget *valueWidget = new ValueWidget(parent);
0393     valueWidget->setWordWrap(true);
0394     valueWidget->setAlignment(Qt::AlignTop | Qt::AlignLeft);
0395     valueWidget->setText(m_readOnly ? plainText(value) : value);
0396     connect(valueWidget, SIGNAL(linkActivated(QString)), q, SLOT(slotLinkActivated(QString)));
0397     return valueWidget;
0398 }
0399 
0400 KFileMetaDataProvider::KFileMetaDataProvider(QObject *parent) :
0401     QObject(parent),
0402     d(new Private(this))
0403 {
0404 }
0405 
0406 KFileMetaDataProvider::~KFileMetaDataProvider()
0407 {
0408     delete d;
0409 }
0410 
0411 void KFileMetaDataProvider::setItems(const KFileItemList &items)
0412 {
0413     d->m_fileItems = items;
0414 
0415 #if ! KIO_NO_NEPOMUK
0416     if (items.isEmpty()) {
0417         return;
0418     }
0419     Q_PRIVATE_SLOT(d,void slotDataChangeStarted())
0420     Q_PRIVATE_SLOT(d,void slotDataChangeFinished())
0421     QList<QUrl> urls;
0422     foreach (const KFileItem &item, items) {
0423         const QUrl url = item.nepomukUri();
0424         if (url.isValid()) {
0425             urls.append(url);
0426         }
0427     }
0428 
0429     d->m_latestMetaDataReader = new KFileMetaDataReader(urls);
0430     d->m_latestMetaDataReader->setReadContextData(d->m_nepomukActivated);
0431     connect(d->m_latestMetaDataReader, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
0432     d->m_metaDataReaders.append(d->m_latestMetaDataReader);
0433     d->m_latestMetaDataReader->start();
0434 #endif
0435 }
0436 
0437 QString KFileMetaDataProvider::label(const QUrl &metaDataUri) const
0438 {
0439     struct TranslationItem {
0440         const char *const key;
0441         const char *const context;
0442         const char *const value;
0443     };
0444 
0445     static const TranslationItem translations[] = {
0446         { "kfileitem#comment", I18N_NOOP2_NOSTRIP("@label", "Comment") },
0447         { "kfileitem#modified", I18N_NOOP2_NOSTRIP("@label", "Modified") },
0448         { "kfileitem#owner", I18N_NOOP2_NOSTRIP("@label", "Owner") },
0449         { "kfileitem#permissions", I18N_NOOP2_NOSTRIP("@label", "Permissions") },
0450         { "kfileitem#rating", I18N_NOOP2_NOSTRIP("@label", "Rating") },
0451         { "kfileitem#size", I18N_NOOP2_NOSTRIP("@label", "Size") },
0452         { "kfileitem#tags", I18N_NOOP2_NOSTRIP("@label", "Tags") },
0453         { "kfileitem#totalSize", I18N_NOOP2_NOSTRIP("@label", "Total Size") },
0454         { "kfileitem#type", I18N_NOOP2_NOSTRIP("@label", "Type") },
0455         { 0, 0, 0} // Mandatory last entry
0456     };
0457 
0458     static QHash<QString, QString> hash;
0459     if (hash.isEmpty()) {
0460         const TranslationItem *item = &translations[0];
0461         while (item->key != 0) {
0462             hash.insert(item->key, i18nc(item->context, item->value));
0463             ++item;
0464         }
0465     }
0466 
0467     QString value = hash.value(metaDataUri.url());
0468     if (value.isEmpty()) {
0469         value = KNfoTranslator::instance().translation(metaDataUri);
0470     }
0471 
0472     return value;
0473 }
0474 
0475 QString KFileMetaDataProvider::group(const QUrl &metaDataUri) const
0476 {
0477     QString group; // return value
0478 
0479     const QString uri = metaDataUri.url();
0480     if (uri == QLatin1String("kfileitem#type")) {
0481         group = QLatin1String("0FileItemA");
0482     } else if (uri == QLatin1String("kfileitem#size")) {
0483         group = QLatin1String("0FileItemB");
0484     } else if (uri == QLatin1String("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#width")) {
0485         group = QLatin1String("0SizeA");
0486     } else if (uri == QLatin1String("http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#height")) {
0487         group = QLatin1String("0SizeB");
0488     }
0489 
0490     return group;
0491 }
0492 
0493 KFileItemList KFileMetaDataProvider::items() const
0494 {
0495     return d->m_fileItems;
0496 }
0497 
0498 void KFileMetaDataProvider::setReadOnly(bool readOnly)
0499 {
0500     d->m_readOnly = readOnly;
0501 }
0502 
0503 bool KFileMetaDataProvider::isReadOnly() const
0504 {
0505     return d->m_readOnly;
0506 }
0507 
0508 #if ! KIO_NO_NEPOMUK
0509 QHash<QUrl, Nepomuk::Variant> KFileMetaDataProvider::data() const
0510 {
0511     return d->m_data;
0512 }
0513 
0514 QWidget *KFileMetaDataProvider::createValueWidget(const QUrl &metaDataUri,
0515         const Nepomuk::Variant &value,
0516         QWidget *parent) const
0517 {
0518     Q_ASSERT(parent != 0);
0519     QWidget *widget = 0;
0520 
0521     if (d->m_nepomukActivated) {
0522         const QString uri = metaDataUri.url();
0523         if (uri == QLatin1String("kfileitem#rating")) {
0524             widget = d->createRatingWidget(value.toInt(), parent);
0525         } else if (uri == QLatin1String("kfileitem#tags")) {
0526             const QStringList tagNames = value.toStringList();
0527             QList<Nepomuk::Tag> tags;
0528             foreach (const QString &tagName, tagNames) {
0529                 tags.append(Nepomuk::Tag(tagName));
0530             }
0531 
0532             widget = d->createTagWidget(tags, parent);
0533         } else if (uri == QLatin1String("kfileitem#comment")) {
0534             widget = d->createCommentWidget(value.toString(), parent);
0535         }
0536     }
0537 
0538     if (widget == 0) {
0539         widget = d->createValueWidget(value.toString(), parent);
0540     }
0541 
0542     widget->setForegroundRole(parent->foregroundRole());
0543     widget->setFont(parent->font());
0544 
0545     return widget;
0546 }
0547 #endif
0548 
0549 int KFileMetaDataProvider::Private::subDirectoriesCount(const QString &path)
0550 {
0551 #ifdef Q_OS_WIN
0552     QDir dir(path);
0553     return dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::System).count();
0554 #else
0555     // Taken from kdelibs/kio/kio/kdirmodel.cpp
0556     // Copyright (C) 2006 David Faure <faure@kde.org>
0557 
0558     int count = -1;
0559     DIR *dir = ::opendir(QFile::encodeName(path));
0560     if (dir) {
0561         count = 0;
0562         struct dirent *dirEntry = 0;
0563         while ((dirEntry = ::readdir(dir))) { // krazy:exclude=syscalls
0564             if (dirEntry->d_name[0] == '.') {
0565                 if (dirEntry->d_name[1] == '\0') {
0566                     // Skip "."
0567                     continue;
0568                 }
0569                 if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') {
0570                     // Skip ".."
0571                     continue;
0572                 }
0573             }
0574             ++count;
0575         }
0576         ::closedir(dir);
0577     }
0578     return count;
0579 #endif
0580 }
0581 
0582 #include "moc_kfilemetadataprovider_p.cpp"