]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/information/metadatawidget.cpp
SVN_SILENT made messages (.desktop file)
[dolphin.git] / src / panels / information / metadatawidget.cpp
1 /***************************************************************************
2 * Copyright (C) 2008 by Sebastian Trueg <trueg@kde.org> *
3 * Copyright (C) 2009 by Peter Penz <peter.penz@gmx.at> *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
19 ***************************************************************************/
20
21 #include "metadatawidget.h"
22
23 #include "metadataconfigurationdialog.h"
24
25 #include <kconfig.h>
26 #include <kconfiggroup.h>
27 #include <kfileitem.h>
28 #include <kglobalsettings.h>
29 #include <klocale.h>
30
31 #include <QFontMetrics>
32 #include <QGridLayout>
33 #include <QLabel>
34 #include <QList>
35 #include <QString>
36
37 #include <config-nepomuk.h>
38 #ifdef HAVE_NEPOMUK
39 #define DISABLE_NEPOMUK_LEGACY
40
41 #include "commentwidget_p.h"
42 #include "nepomukmassupdatejob_p.h"
43 #include "taggingwidget_p.h"
44
45 #include <Nepomuk/KRatingWidget>
46 #include <Nepomuk/Resource>
47 #include <Nepomuk/Types/Property>
48 #include <Nepomuk/Variant>
49
50 #include <Soprano/Vocabulary/Xesam>
51 #include <QMutex>
52 #include <QSpacerItem>
53 #include <QThread>
54 #endif
55
56 class MetaDataWidget::Private
57 {
58 public:
59 struct Row
60 {
61 QLabel* label;
62 QWidget* infoWidget;
63 };
64
65 Private(MetaDataWidget* parent);
66 ~Private();
67
68 void addRow(QLabel* label, QWidget* infoWidget);
69 void removeMetaInfoRows();
70 void setRowVisible(QWidget* infoWidget, bool visible);
71
72 /**
73 * Initializes the configuration file "kmetainformationrc"
74 * with proper default settings for the first start in
75 * an uninitialized environment.
76 */
77 void initMetaInfoSettings();
78
79 /**
80 * Parses the configuration file "kmetainformationrc" and
81 * updates the visibility of all rows.
82 */
83 void updateRowsVisibility();
84
85 void slotLoadingFinished();
86 void slotRatingChanged(unsigned int rating);
87 void slotTagsChanged(const QList<Nepomuk::Tag>& tags);
88 void slotCommentChanged(const QString& comment);
89 void slotMetaDataUpdateDone();
90
91 /**
92 * Disables the metadata widget and starts the job that
93 * changes the meta data asynchronously. After the job
94 * has been finished, the metadata widget gets enabled again.
95 */
96 void startChangeDataJob(KJob* job);
97
98 QList<KFileItem> m_fileItems;
99 QList<Row> m_rows;
100
101 QGridLayout* m_gridLayout;
102
103 QLabel* m_typeInfo;
104 QLabel* m_sizeLabel;
105 QLabel* m_sizeInfo;
106 QLabel* m_modifiedInfo;
107 QLabel* m_ownerInfo;
108 QLabel* m_permissionsInfo;
109
110 #ifdef HAVE_NEPOMUK
111 KRatingWidget* m_ratingWidget;
112 TaggingWidget* m_taggingWidget;
113 CommentWidget* m_commentWidget;
114
115 // shared data between the GUI-thread and
116 // the loader-thread (see LoadFilesThread):
117 QMutex m_mutex;
118 struct SharedData
119 {
120 int rating;
121 QString comment;
122 QList<Nepomuk::Tag> tags;
123 QList<QString> metaInfoLabels;
124 QList<QString> metaInfoValues;
125 QMap<KUrl, Nepomuk::Resource> files;
126 } m_sharedData;
127
128 /**
129 * Loads the meta data of files and writes
130 * the result into a shared data pool that
131 * can be used by the widgets in the GUI thread.
132 */
133 class LoadFilesThread : public QThread
134 {
135 public:
136 LoadFilesThread(SharedData* m_sharedData, QMutex* m_mutex);
137 virtual ~LoadFilesThread();
138 void loadFiles(const KUrl::List& urls);
139 virtual void run();
140
141 private:
142 /**
143 * Assures that the settings for the meta information
144 * are initialized with proper default values.
145 */
146 void initMetaInfoSettings(KConfigGroup& group);
147
148 /**
149 * Temporary helper method for KDE 4.3 as we currently don't get
150 * translated labels for Nepmok literals: Replaces camelcase labels
151 * like "fileLocation" by "File Location:".
152 */
153 QString tunedLabel(const QString& label) const;
154
155 private:
156 SharedData* m_sharedData;
157 QMutex* m_mutex;
158 KUrl::List m_urls;
159 bool m_canceled;
160 };
161
162 LoadFilesThread* m_loadFilesThread;
163 #endif
164
165 private:
166 MetaDataWidget* const q;
167 };
168
169 MetaDataWidget::Private::Private(MetaDataWidget* parent) :
170 m_fileItems(),
171 m_rows(),
172 m_gridLayout(0),
173 m_typeInfo(0),
174 m_sizeLabel(0),
175 m_sizeInfo(0),
176 m_modifiedInfo(0),
177 m_ownerInfo(0),
178 m_permissionsInfo(0),
179 #ifdef HAVE_NEPOMUK
180 m_ratingWidget(0),
181 m_taggingWidget(0),
182 m_commentWidget(0),
183 m_loadFilesThread(0),
184 #endif
185 q(parent)
186 {
187 m_gridLayout = new QGridLayout(parent);
188 m_gridLayout->setMargin(0);
189
190 m_typeInfo = new QLabel(parent);
191 m_sizeLabel = new QLabel(parent);
192 m_sizeInfo = new QLabel(parent);
193 m_modifiedInfo = new QLabel(parent);
194 m_ownerInfo = new QLabel(parent);
195 m_permissionsInfo = new QLabel(parent);
196 #ifdef HAVE_NEPOMUK
197 const QFontMetrics fontMetrics(KGlobalSettings::smallestReadableFont());
198 m_ratingWidget = new KRatingWidget(parent);
199 m_ratingWidget->setFixedHeight(fontMetrics.height());
200 connect(m_ratingWidget, SIGNAL(ratingChanged(unsigned int)),
201 q, SLOT(slotRatingChanged(unsigned int)));
202
203 m_taggingWidget = new TaggingWidget(parent);
204 connect(m_taggingWidget, SIGNAL(tagsChanged(const QList<Nepomuk::Tag>&)),
205 q, SLOT(slotTagsChanged(const QList<Nepomuk::Tag>&)));
206
207 m_commentWidget = new CommentWidget(parent);
208 connect(m_commentWidget, SIGNAL(commentChanged(const QString&)),
209 q, SLOT(slotCommentChanged(const QString&)));
210 #endif
211
212 addRow(new QLabel(i18nc("@label", "Type:"), parent), m_typeInfo);
213 addRow(m_sizeLabel, m_sizeInfo);
214 addRow(new QLabel(i18nc("@label", "Modified:"), parent), m_modifiedInfo);
215 addRow(new QLabel(i18nc("@label", "Owner:"), parent), m_ownerInfo);
216 addRow(new QLabel(i18nc("@label", "Permissions:"), parent), m_permissionsInfo);
217 #ifdef HAVE_NEPOMUK
218 addRow(new QLabel(i18nc("@label", "Rating:"), parent), m_ratingWidget);
219 addRow(new QLabel(i18nc("@label", "Tags:"), parent), m_taggingWidget);
220 addRow(new QLabel(i18nc("@label", "Comment:"), parent), m_commentWidget);
221
222 m_sharedData.rating = 0;
223 m_loadFilesThread = new LoadFilesThread(&m_sharedData, &m_mutex);
224 connect(m_loadFilesThread, SIGNAL(finished()), q, SLOT(slotLoadingFinished()));
225 #endif
226
227 initMetaInfoSettings();
228 updateRowsVisibility();
229 }
230
231 MetaDataWidget::Private::~Private()
232 {
233 #ifdef HAVE_NEPOMUK
234 delete m_loadFilesThread;
235 #endif
236 }
237
238 void MetaDataWidget::Private::addRow(QLabel* label, QWidget* infoWidget)
239 {
240 Row row;
241 row.label = label;
242 row.infoWidget = infoWidget;
243 m_rows.append(row);
244
245 const QFont smallFont = KGlobalSettings::smallestReadableFont();
246 // use a brighter color for the label and a small font size
247 QPalette palette = label->palette();
248 QColor textColor = palette.color(QPalette::Text);
249 textColor.setAlpha(128);
250 palette.setColor(QPalette::WindowText, textColor);
251 label->setPalette(palette);
252 label->setFont(smallFont);
253 label->setWordWrap(true);
254 label->setAlignment(Qt::AlignTop | Qt::AlignRight);
255
256 QLabel* infoLabel = qobject_cast<QLabel*>(infoWidget);
257 if (infoLabel != 0) {
258 infoLabel->setFont(smallFont);
259 infoLabel->setWordWrap(true);
260 infoLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
261 }
262
263 // add the row to grid layout
264 const int rowIndex = m_rows.count();
265 m_gridLayout->addWidget(label, rowIndex, 0, Qt::AlignRight);
266 const int spacerWidth = QFontMetrics(smallFont).size(Qt::TextSingleLine, " ").width();
267 m_gridLayout->addItem(new QSpacerItem(spacerWidth, 1), rowIndex, 1);
268 m_gridLayout->addWidget(infoWidget, rowIndex, 2, Qt::AlignLeft);
269 }
270
271 void MetaDataWidget::Private::setRowVisible(QWidget* infoWidget, bool visible)
272 {
273 foreach (const Row& row, m_rows) {
274 if (row.infoWidget == infoWidget) {
275 row.label->setVisible(visible);
276 row.infoWidget->setVisible(visible);
277 return;
278 }
279 }
280 }
281
282
283 void MetaDataWidget::Private::initMetaInfoSettings()
284 {
285 KConfig config("kmetainformationrc", KConfig::NoGlobals);
286 KConfigGroup settings = config.group("Show");
287 if (!settings.readEntry("initialized", false)) {
288 // The resource file is read the first time. Assure
289 // that some meta information is disabled per default.
290
291 static const char* disabledProperties[] = {
292 "asText", "contentSize", "created", "depth", "description", "fileExtension",
293 "fileName", "fileSize", "hasTag", "isPartOf", "lastModified", "mimeType", "name",
294 "numericRating", "parentUrl", "permissions", "plainTextContent", "owner",
295 "sourceModified", "url",
296 0 // mandatory last entry
297 };
298
299 int i = 0;
300 while (disabledProperties[i] != 0) {
301 settings.writeEntry(disabledProperties[i], false);
302 ++i;
303 }
304
305 // mark the group as initialized
306 settings.writeEntry("initialized", true);
307 }
308 }
309
310 void MetaDataWidget::Private::updateRowsVisibility()
311 {
312 KConfig config("kmetainformationrc", KConfig::NoGlobals);
313 KConfigGroup settings = config.group("Show");
314 setRowVisible(m_typeInfo, settings.readEntry("type", true));
315 setRowVisible(m_sizeInfo, settings.readEntry("size", true));
316 setRowVisible(m_modifiedInfo, settings.readEntry("modified", true));
317 setRowVisible(m_ownerInfo, settings.readEntry("owner", true));
318 setRowVisible(m_permissionsInfo, settings.readEntry("permissions", true));
319 #ifdef HAVE_NEPOMUK
320 setRowVisible(m_ratingWidget, settings.readEntry("rating", true));
321 setRowVisible(m_taggingWidget, settings.readEntry("tagging", true));
322 setRowVisible(m_commentWidget, settings.readEntry("comment", true));
323 #endif
324 }
325
326 void MetaDataWidget::Private::slotLoadingFinished()
327 {
328 #ifdef HAVE_NEPOMUK
329 QMutexLocker locker(&m_mutex);
330 m_ratingWidget->setRating(m_sharedData.rating);
331 m_commentWidget->setText(m_sharedData.comment);
332 m_taggingWidget->setTags(m_sharedData.tags);
333
334 // Show the remaining meta information as text. The number
335 // of required rows may very. Existing rows are reused to
336 // prevent flickering.
337 int index = 8; // TODO: don't hardcode this value here
338 const int rowCount = m_rows.count();
339 Q_ASSERT(rowCount >= index);
340
341 Q_ASSERT(m_sharedData.metaInfoLabels.count() == m_sharedData.metaInfoValues.count());
342 const int metaInfoCount = m_sharedData.metaInfoLabels.count();
343 for (int i = 0; i < metaInfoCount; ++i) {
344 if (index < rowCount) {
345 // adjust texts of the current row
346 m_rows[index].label->setText(m_sharedData.metaInfoLabels[i]);
347 QLabel* infoValueLabel = qobject_cast<QLabel*>(m_rows[index].infoWidget);
348 Q_ASSERT(infoValueLabel != 0);
349 infoValueLabel->setText(m_sharedData.metaInfoValues[i]);
350 } else {
351 // create new row
352 QLabel* infoLabel = new QLabel(m_sharedData.metaInfoLabels[i], q);
353 QLabel* infoValue = new QLabel(m_sharedData.metaInfoValues[i], q);
354 addRow(infoLabel, infoValue);
355 }
356 ++index;
357 }
358 if (metaInfoCount > 0) {
359 --index;
360 }
361
362 // remove rows that are not needed anymore
363 for (int i = rowCount - 1; i > index; --i) {
364 delete m_rows[i].label;
365 delete m_rows[i].infoWidget;
366 m_rows.pop_back();
367 }
368
369 emit q->loadingFinished();
370 #endif
371 }
372
373 void MetaDataWidget::Private::slotRatingChanged(unsigned int rating)
374 {
375 #ifdef HAVE_NEPOMUK
376 QMutexLocker locker(&m_mutex);
377 Nepomuk::MassUpdateJob* job =
378 Nepomuk::MassUpdateJob::rateResources(m_sharedData.files.values(), rating);
379 locker.unlock();
380 startChangeDataJob(job);
381 #endif
382 emit q->ratingChanged(rating);
383 }
384
385 void MetaDataWidget::Private::slotTagsChanged(const QList<Nepomuk::Tag>& tags)
386 {
387 #ifdef HAVE_NEPOMUK
388 m_taggingWidget->setTags(tags);
389
390 QMutexLocker locker(&m_mutex);
391 Nepomuk::MassUpdateJob* job =
392 Nepomuk::MassUpdateJob::tagResources(m_sharedData.files.values(), tags);
393 locker.unlock();
394 startChangeDataJob(job);
395 #endif
396 emit q->tagsChanged(tags);
397 }
398
399 void MetaDataWidget::Private::slotCommentChanged(const QString& comment)
400 {
401 #ifdef HAVE_NEPOMUK
402 QMutexLocker locker(&m_mutex);
403 Nepomuk::MassUpdateJob* job =
404 Nepomuk::MassUpdateJob::commentResources(m_sharedData.files.values(), comment);
405 locker.unlock();
406 startChangeDataJob(job);
407 #endif
408 emit q->commentChanged(comment);
409 }
410
411 void MetaDataWidget::Private::slotMetaDataUpdateDone()
412 {
413 q->setEnabled(true);
414 }
415
416 void MetaDataWidget::Private::startChangeDataJob(KJob* job)
417 {
418 connect(job, SIGNAL(result(KJob*)),
419 q, SLOT(slotMetaDataUpdateDone()));
420 q->setEnabled(false); // no updates during execution
421 job->start();
422 }
423
424 #ifdef HAVE_NEPOMUK
425 MetaDataWidget::Private::LoadFilesThread::LoadFilesThread(
426 MetaDataWidget::Private::SharedData* m_sharedData,
427 QMutex* m_mutex) :
428 m_sharedData(m_sharedData),
429 m_mutex(m_mutex),
430 m_urls(),
431 m_canceled(false)
432 {
433 }
434
435 MetaDataWidget::Private::LoadFilesThread::~LoadFilesThread()
436 {
437 // This thread may very well be deleted during execution. We need
438 // to protect it from crashes here.
439 m_canceled = true;
440 wait();
441 }
442
443 void MetaDataWidget::Private::LoadFilesThread::loadFiles(const KUrl::List& urls)
444 {
445 QMutexLocker locker(m_mutex);
446 m_urls = urls;
447 m_canceled = false;
448 start();
449 }
450
451 void MetaDataWidget::Private::LoadFilesThread::run()
452 {
453 QMutexLocker locker(m_mutex);
454 const KUrl::List urls = m_urls;
455 locker.unlock();
456
457 KConfig config("kmetainformationrc", KConfig::NoGlobals);
458 KConfigGroup settings = config.group("Show");
459
460 bool first = true;
461 unsigned int rating = 0;
462 QString comment;
463 QList<Nepomuk::Tag> tags;
464 QList<QString> metaInfoLabels;
465 QList<QString> metaInfoValues;
466 QMap<KUrl, Nepomuk::Resource> files;
467 foreach (const KUrl& url, urls) {
468 if (m_canceled) {
469 return;
470 }
471
472 Nepomuk::Resource file(url, Soprano::Vocabulary::Xesam::File());
473 files.insert(url, file);
474
475 if (!first && (rating != file.rating())) {
476 rating = 0; // reset rating
477 } else if (first) {
478 rating = file.rating();
479 }
480
481 if (!first && (comment != file.description())) {
482 comment.clear(); // reset comment
483 } else if (first) {
484 comment = file.description();
485 }
486
487 if (!first && (tags != file.tags())) {
488 tags.clear(); // reset tags
489 } else if (first) {
490 tags = file.tags();
491 }
492
493 if (first && (urls.count() == 1)) {
494 // TODO: show shared meta informations instead
495 // of not showing anything on multiple selections
496 QHash<QUrl, Nepomuk::Variant> properties = file.properties();
497 QHash<QUrl, Nepomuk::Variant>::const_iterator it = properties.constBegin();
498 while (it != properties.constEnd()) {
499 Nepomuk::Types::Property prop(it.key());
500 if (settings.readEntry(prop.name(), true)) {
501 // TODO #1: use Nepomuk::formatValue(res, prop) if available
502 // instead of it.value().toString()
503 // TODO #2: using tunedLabel() is a workaround for KDE 4.3 until
504 // we get translated labels
505 metaInfoLabels.append(tunedLabel(prop.label()));
506 metaInfoValues.append(it.value().toString());
507 }
508 ++it;
509 }
510 }
511
512 first = false;
513 }
514
515 locker.relock();
516 m_sharedData->rating = rating;
517 m_sharedData->comment = comment;
518 m_sharedData->tags = tags;
519 m_sharedData->metaInfoLabels = metaInfoLabels;
520 m_sharedData->metaInfoValues = metaInfoValues;
521 m_sharedData->files = files;
522 }
523
524 QString MetaDataWidget::Private::LoadFilesThread::tunedLabel(const QString& label) const
525 {
526 QString tunedLabel;
527 const int labelLength = label.length();
528 if (labelLength > 0) {
529 tunedLabel.reserve(labelLength);
530 tunedLabel = label[0].toUpper();
531 for (int i = 1; i < labelLength; ++i) {
532 if (label[i].isUpper() && !label[i - 1].isSpace() && !label[i - 1].isUpper()) {
533 tunedLabel += ' ';
534 tunedLabel += label[i].toLower();
535 } else {
536 tunedLabel += label[i];
537 }
538 }
539 }
540 return tunedLabel + ':';
541 }
542
543 #endif // HAVE_NEPOMUK
544
545 MetaDataWidget::MetaDataWidget(QWidget* parent) :
546 QWidget(parent),
547 d(new Private(this))
548 {
549 }
550
551 MetaDataWidget::~MetaDataWidget()
552 {
553 delete d;
554 }
555
556 void MetaDataWidget::setItem(const KFileItem& item)
557 {
558 // update values for "type", "size", "modified",
559 // "owner" and "permissions" synchronously
560 d->m_sizeLabel->setText(i18nc("@label", "Size:"));
561 if (item.isDir()) {
562 d->m_typeInfo->setText(i18nc("@label", "Folder"));
563 d->setRowVisible(d->m_sizeInfo, false);
564 } else {
565 d->m_typeInfo->setText(item.mimeComment());
566 d->m_sizeInfo->setText(KIO::convertSize(item.size()));
567 d->setRowVisible(d->m_sizeInfo, true);
568 }
569 d->m_modifiedInfo->setText(item.timeString());
570 d->m_ownerInfo->setText(item.user());
571 d->m_permissionsInfo->setText(item.permissionsString());
572
573 setItems(KFileItemList() << item);
574 }
575
576 void MetaDataWidget::setItems(const KFileItemList& items)
577 {
578 d->m_fileItems = items;
579
580 if (items.count() > 1) {
581 // calculate the size of all items and show this
582 // information to the user
583 d->m_sizeLabel->setText(i18nc("@label", "Total Size:"));
584 d->setRowVisible(d->m_sizeInfo, true);
585
586 quint64 totalSize = 0;
587 foreach (const KFileItem& item, items) {
588 if (!item.isDir() && !item.isLink()) {
589 totalSize += item.size();
590 }
591 }
592 d->m_sizeInfo->setText(KIO::convertSize(totalSize));
593 }
594
595 #ifdef HAVE_NEPOMUK
596 QList<KUrl> urls;
597 foreach (const KFileItem& item, items) {
598 const KUrl url = item.nepomukUri();
599 if (url.isValid()) {
600 urls.append(url);
601 }
602 }
603 d->m_loadFilesThread->loadFiles(urls);
604 #endif
605 }
606
607 void MetaDataWidget::openConfigurationDialog()
608 {
609 const KUrl url = d->m_fileItems[0].nepomukUri();
610 if (!url.isValid()) {
611 return;
612 }
613
614 MetaDataConfigurationDialog dialog(url, this, Qt::Dialog);
615 if (dialog.exec() == KDialog::Accepted) {
616 d->updateRowsVisibility();
617 }
618 }
619
620 unsigned int MetaDataWidget::rating() const
621 {
622 #ifdef HAVE_NEPOMUK
623 QMutexLocker locker(&d->m_mutex);
624 return d->m_sharedData.rating;
625 #else
626 return 0;
627 #endif
628 }
629
630 QList<Nepomuk::Tag> MetaDataWidget::tags() const
631 {
632 #ifdef HAVE_NEPOMUK
633 QMutexLocker locker(&d->m_mutex);
634 return d->m_sharedData.tags;
635 #else
636 return QList<Nepomuk::Tag>();
637 #endif
638 }
639
640 QString MetaDataWidget::comment() const
641 {
642 #ifdef HAVE_NEPOMUK
643 QMutexLocker locker(&d->m_mutex);
644 return d->m_sharedData.comment;
645 #else
646 return QString();
647 #endif
648 }
649
650 #include "metadatawidget.moc"