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