File indexing completed on 2025-01-12 12:26:24
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"