]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/information/kmetadatawidget.cpp
* Fixed execution of links in the metadata widget.
[dolphin.git] / src / panels / information / kmetadatawidget.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 library is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU Library General Public *
7 * License version 2 as published by the Free Software Foundation. *
8 * *
9 * This library is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
12 * Library General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU Library General Public License *
15 * along with this library; see the file COPYING.LIB. If not, write to *
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
17 * Boston, MA 02110-1301, USA. *
18 *****************************************************************************/
19
20 #include "kmetadatawidget.h"
21
22 #include <kconfig.h>
23 #include <kconfiggroup.h>
24 #include <kfileitem.h>
25 #include <kglobalsettings.h>
26 #include <kglobal.h>
27 #include <klocale.h>
28
29 #include <QEvent>
30 #include <QFontMetrics>
31 #include <QGridLayout>
32 #include <QLabel>
33 #include <QList>
34 #include <QString>
35
36 #include <config-nepomuk.h>
37 #ifdef HAVE_NEPOMUK
38 #define DISABLE_NEPOMUK_LEGACY
39
40 #include "kcommentwidget_p.h"
41 #include "kloadmetadatathread_p.h"
42 #include "ktaggingwidget_p.h"
43
44 #include <nepomuk/kratingwidget.h>
45 #include <nepomuk/resource.h>
46 #include <nepomuk/resourcemanager.h>
47 #include <nepomuk/property.h>
48 #include <nepomuk/tag.h>
49 #include <nepomuk/variant.h>
50 #include "nepomukmassupdatejob_p.h"
51
52 #include <QMutex>
53 #include <QSpacerItem>
54 #include <QThread>
55 #else
56 namespace Nepomuk
57 {
58 typedef int Tag;
59 }
60 #endif
61
62 class KMetaDataWidget::Private
63 {
64 public:
65 struct Row
66 {
67 QLabel* label;
68 QWidget* infoWidget;
69 };
70
71 Private(KMetaDataWidget* parent);
72 ~Private();
73
74 void addRow(QLabel* label, QWidget* infoWidget);
75 void removeMetaInfoRows();
76 void setRowVisible(QWidget* infoWidget, bool visible);
77
78 /**
79 * Initializes the configuration file "kmetainformationrc"
80 * with proper default settings for the first start in
81 * an uninitialized environment.
82 */
83 void initMetaInfoSettings();
84
85 /**
86 * Parses the configuration file "kmetainformationrc" and
87 * updates the visibility of all rows.
88 */
89 void updateRowsVisibility();
90
91 void slotLoadingFinished();
92
93 void slotRatingChanged(unsigned int rating);
94 void slotTagsChanged(const QList<Nepomuk::Tag>& tags);
95 void slotCommentChanged(const QString& comment);
96
97 void slotMetaDataUpdateDone();
98 void slotLinkActivated(const QString& link);
99
100 #ifdef HAVE_NEPOMUK
101 /**
102 * Disables the metadata widget and starts the job that
103 * changes the meta data asynchronously. After the job
104 * has been finished, the metadata widget gets enabled again.
105 */
106 void startChangeDataJob(KJob* job);
107
108 /**
109 * Merges items like 'width' and 'height' as one item.
110 */
111 QList<KLoadMetaDataThread::Item> mergedItems(const QList<KLoadMetaDataThread::Item>& items);
112 #endif
113
114 bool m_sizeVisible;
115 bool m_readOnly;
116 bool m_nepomukActivated;
117 MetaDataTypes m_visibleDataTypes;
118 QList<KFileItem> m_fileItems;
119 QList<Row> m_rows;
120
121 QGridLayout* m_gridLayout;
122
123 QLabel* m_typeInfo;
124 QLabel* m_sizeLabel;
125 QLabel* m_sizeInfo;
126 QLabel* m_modifiedInfo;
127 QLabel* m_ownerInfo;
128 QLabel* m_permissionsInfo;
129
130 #ifdef HAVE_NEPOMUK
131 KRatingWidget* m_ratingWidget;
132 KTaggingWidget* m_taggingWidget;
133 KCommentWidget* m_commentWidget;
134
135 QMap<KUrl, Nepomuk::Resource> m_files;
136
137 KLoadMetaDataThread* m_loadMetaDataThread;
138 #endif
139
140 private:
141 KMetaDataWidget* const q;
142 };
143
144 KMetaDataWidget::Private::Private(KMetaDataWidget* parent) :
145 m_sizeVisible(true),
146 m_readOnly(false),
147 m_nepomukActivated(false),
148 m_visibleDataTypes(TypeData | SizeData | ModifiedData | OwnerData |
149 PermissionsData | RatingData | TagsData | CommentData),
150 m_fileItems(),
151 m_rows(),
152 m_gridLayout(0),
153 m_typeInfo(0),
154 m_sizeLabel(0),
155 m_sizeInfo(0),
156 m_modifiedInfo(0),
157 m_ownerInfo(0),
158 m_permissionsInfo(0),
159 #ifdef HAVE_NEPOMUK
160 m_ratingWidget(0),
161 m_taggingWidget(0),
162 m_commentWidget(0),
163 m_files(),
164 m_loadMetaDataThread(0),
165 #endif
166 q(parent)
167 {
168 const QFontMetrics fontMetrics(KGlobalSettings::smallestReadableFont());
169
170 m_gridLayout = new QGridLayout(parent);
171 m_gridLayout->setMargin(0);
172 m_gridLayout->setSpacing(fontMetrics.height() / 4);
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
181 #ifdef HAVE_NEPOMUK
182 m_nepomukActivated = (Nepomuk::ResourceManager::instance()->init() == 0);
183 if (m_nepomukActivated) {
184 m_ratingWidget = new KRatingWidget(parent);
185 m_ratingWidget->setFixedHeight(fontMetrics.height());
186 connect(m_ratingWidget, SIGNAL(ratingChanged(unsigned int)),
187 q, SLOT(slotRatingChanged(unsigned int)));
188
189 m_taggingWidget = new KTaggingWidget(parent);
190 connect(m_taggingWidget, SIGNAL(tagsChanged(const QList<Nepomuk::Tag>&)),
191 q, SLOT(slotTagsChanged(const QList<Nepomuk::Tag>&)));
192
193 m_commentWidget = new KCommentWidget(parent);
194 connect(m_commentWidget, SIGNAL(commentChanged(const QString&)),
195 q, SLOT(slotCommentChanged(const QString&)));
196 }
197 #endif
198
199 initMetaInfoSettings();
200 }
201
202 KMetaDataWidget::Private::~Private()
203 {
204 #ifdef HAVE_NEPOMUK
205 if (m_loadMetaDataThread != 0) {
206 disconnect(m_loadMetaDataThread, SIGNAL(finished()), q, SLOT(slotLoadingFinished()));
207 m_loadMetaDataThread->cancelAndDelete();
208 m_loadMetaDataThread = 0;
209 }
210 #endif
211 }
212
213 void KMetaDataWidget::Private::addRow(QLabel* label, QWidget* infoWidget)
214 {
215 Row row;
216 row.label = label;
217 row.infoWidget = infoWidget;
218 m_rows.append(row);
219
220 const QFont smallFont = KGlobalSettings::smallestReadableFont();
221 // use a brighter color for the label and a small font size
222 QPalette palette = label->palette();
223 const QPalette::ColorRole role = q->foregroundRole();
224 QColor textColor = palette.color(role);
225 textColor.setAlpha(128);
226 palette.setColor(role, textColor);
227 label->setPalette(palette);
228 label->setForegroundRole(role);
229 label->setFont(smallFont);
230 label->setWordWrap(true);
231 label->setAlignment(Qt::AlignTop | Qt::AlignRight);
232
233 infoWidget->setForegroundRole(role);
234 QLabel* infoLabel = qobject_cast<QLabel*>(infoWidget);
235 if (infoLabel != 0) {
236 infoLabel->setFont(smallFont);
237 infoLabel->setWordWrap(true);
238 infoLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
239 }
240
241 // add the row to grid layout
242 const int rowIndex = m_rows.count();
243 m_gridLayout->addWidget(label, rowIndex, 0, Qt::AlignRight);
244 const int spacerWidth = QFontMetrics(smallFont).size(Qt::TextSingleLine, " ").width();
245 m_gridLayout->addItem(new QSpacerItem(spacerWidth, 1), rowIndex, 1);
246 m_gridLayout->addWidget(infoWidget, rowIndex, 2, Qt::AlignLeft);
247 }
248
249 void KMetaDataWidget::Private::setRowVisible(QWidget* infoWidget, bool visible)
250 {
251 foreach (const Row& row, m_rows) {
252 if (row.infoWidget == infoWidget) {
253 row.label->setVisible(visible);
254 row.infoWidget->setVisible(visible);
255 return;
256 }
257 }
258 }
259
260
261 void KMetaDataWidget::Private::initMetaInfoSettings()
262 {
263 KConfig config("kmetainformationrc", KConfig::NoGlobals);
264 KConfigGroup settings = config.group("Show");
265 if (!settings.readEntry("initialized", false)) {
266 // The resource file is read the first time. Assure
267 // that some meta information is disabled per default.
268
269 static const char* disabledProperties[] = {
270 "asText", "contentSize", "created", "depth", "description", "fileExtension",
271 "fileName", "fileSize", "hasTag", "isPartOf", "lastModified", "mimeType", "name",
272 "numericRating", "parentUrl", "permissions", "plainTextContent", "owner",
273 "sourceModified", "url",
274 0 // mandatory last entry
275 };
276
277 int i = 0;
278 while (disabledProperties[i] != 0) {
279 settings.writeEntry(disabledProperties[i], false);
280 ++i;
281 }
282
283 // mark the group as initialized
284 settings.writeEntry("initialized", true);
285 }
286 }
287
288 void KMetaDataWidget::Private::updateRowsVisibility()
289 {
290 KConfig config("kmetainformationrc", KConfig::NoGlobals);
291 KConfigGroup settings = config.group("Show");
292
293 setRowVisible(m_typeInfo,
294 (m_visibleDataTypes & KMetaDataWidget::TypeData) &&
295 settings.readEntry("type", true));
296
297 // Cache in m_sizeVisible whether the size should be shown. This
298 // is necessary as the size is temporary hidden when the target
299 // file item is a directory.
300 m_sizeVisible = (m_visibleDataTypes & KMetaDataWidget::SizeData) &&
301 settings.readEntry("size", true);
302 setRowVisible(m_sizeInfo, m_sizeVisible);
303
304 setRowVisible(m_modifiedInfo,
305 (m_visibleDataTypes & KMetaDataWidget::ModifiedData) &&
306 settings.readEntry("modified", true));
307
308 setRowVisible(m_ownerInfo,
309 (m_visibleDataTypes & KMetaDataWidget::OwnerData) &&
310 settings.readEntry("owner", true));
311
312 setRowVisible(m_permissionsInfo,
313 (m_visibleDataTypes & KMetaDataWidget::PermissionsData) &&
314 settings.readEntry("permissions", true));
315
316 #ifdef HAVE_NEPOMUK
317 if (m_nepomukActivated) {
318 setRowVisible(m_ratingWidget,
319 (m_visibleDataTypes & KMetaDataWidget::RatingData) &&
320 settings.readEntry("rating", true));
321
322 setRowVisible(m_taggingWidget,
323 (m_visibleDataTypes & KMetaDataWidget::TagsData) &&
324 settings.readEntry("tags", true));
325
326 setRowVisible(m_commentWidget,
327 (m_visibleDataTypes & KMetaDataWidget::CommentData) &&
328 settings.readEntry("comment", true));
329 }
330 #endif
331 }
332
333 void KMetaDataWidget::Private::slotLoadingFinished()
334 {
335 #ifdef HAVE_NEPOMUK
336 Q_ASSERT(m_loadMetaDataThread != 0);
337 Q_ASSERT(m_ratingWidget != 0);
338 Q_ASSERT(m_commentWidget != 0);
339 Q_ASSERT(m_taggingWidget != 0);
340 m_ratingWidget->setRating(m_loadMetaDataThread->rating());
341 m_commentWidget->setText(m_loadMetaDataThread->comment());
342 m_taggingWidget->setTags(m_loadMetaDataThread->tags());
343
344 // Show the remaining meta information as text. The number
345 // of required rows may very. Existing rows are reused to
346 // prevent flickering.
347 int index = 8; // TODO: don't hardcode this value here
348 const int rowCount = m_rows.count();
349 Q_ASSERT(rowCount >= index);
350
351 const QList<KLoadMetaDataThread::Item> items = mergedItems(m_loadMetaDataThread->items());
352 foreach (const KLoadMetaDataThread::Item& item, items) {
353 if (index < rowCount) {
354 // adjust texts of the current row
355 m_rows[index].label->setText(item.label);
356 QLabel* infoValueLabel = qobject_cast<QLabel*>(m_rows[index].infoWidget);
357 Q_ASSERT(infoValueLabel != 0);
358 infoValueLabel->setText(item.value);
359 } else {
360 // create new row
361 QLabel* infoLabel = new QLabel(item.label, q);
362 QLabel* infoValue = new QLabel(item.value, q);
363 connect(infoValue, SIGNAL(linkActivated(QString)),
364 q, SLOT(slotLinkActivated(QString)));
365 addRow(infoLabel, infoValue);
366 }
367 ++index;
368 }
369 if (items.count() > 0) {
370 --index;
371 }
372
373 // remove rows that are not needed anymore
374 for (int i = m_rows.count() - 1; i >= index; --i) {
375 delete m_rows[i].label;
376 delete m_rows[i].infoWidget;
377 m_rows.pop_back();
378 }
379
380 m_files = m_loadMetaDataThread->files();
381
382 delete m_loadMetaDataThread;
383 m_loadMetaDataThread = 0;
384 #endif
385
386 q->updateGeometry();
387 }
388
389 void KMetaDataWidget::Private::slotRatingChanged(unsigned int rating)
390 {
391 #ifdef HAVE_NEPOMUK
392 Nepomuk::MassUpdateJob* job =
393 Nepomuk::MassUpdateJob::rateResources(m_files.values(), rating);
394 startChangeDataJob(job);
395 #else
396 Q_UNUSED(rating);
397 #endif
398 }
399
400 void KMetaDataWidget::Private::slotTagsChanged(const QList<Nepomuk::Tag>& tags)
401 {
402 #ifdef HAVE_NEPOMUK
403 m_taggingWidget->setTags(tags);
404
405 Nepomuk::MassUpdateJob* job =
406 Nepomuk::MassUpdateJob::tagResources(m_files.values(), tags);
407 startChangeDataJob(job);
408 #else
409 Q_UNUSED(tags);
410 #endif
411 }
412
413 void KMetaDataWidget::Private::slotCommentChanged(const QString& comment)
414 {
415 #ifdef HAVE_NEPOMUK
416 Nepomuk::MassUpdateJob* job =
417 Nepomuk::MassUpdateJob::commentResources(m_files.values(), comment);
418 startChangeDataJob(job);
419 #else
420 Q_UNUSED(comment);
421 #endif
422 }
423
424 void KMetaDataWidget::Private::slotMetaDataUpdateDone()
425 {
426 #ifdef HAVE_NEPOMUK
427 q->setEnabled(true);
428 #endif
429 }
430
431 void KMetaDataWidget::Private::slotLinkActivated(const QString& link)
432 {
433 emit q->urlActivated(KUrl(link));
434 }
435
436 #ifdef HAVE_NEPOMUK
437 void KMetaDataWidget::Private::startChangeDataJob(KJob* job)
438 {
439 connect(job, SIGNAL(result(KJob*)),
440 q, SLOT(slotMetaDataUpdateDone()));
441 q->setEnabled(false); // no updates during execution
442 job->start();
443 }
444
445 QList<KLoadMetaDataThread::Item>
446 KMetaDataWidget::Private::mergedItems(const QList<KLoadMetaDataThread::Item>& items)
447 {
448 // TODO: Currently only "width" and "height" are merged as "width x height". If
449 // other kind of merges should be done too, a more general approach is required.
450 QList<KLoadMetaDataThread::Item> mergedItems;
451
452 KLoadMetaDataThread::Item width;
453 KLoadMetaDataThread::Item height;
454
455 foreach (const KLoadMetaDataThread::Item& item, items) {
456 if (item.name == "width") {
457 width = item;
458 } else if (item.name == "height") {
459 height = item;
460 } else {
461 // insert the item sorted by the label
462 bool inserted = false;
463 int i = 0;
464 const int count = mergedItems.count();
465 while (!inserted && (i < count)) {
466 if (item.label < mergedItems[i].label) {
467 mergedItems.insert(i, item);
468 inserted = true;
469 }
470 ++i;
471 }
472 if (!inserted) {
473 mergedItems.append(item);
474 }
475 }
476 }
477
478 const bool foundWidth = !width.name.isEmpty();
479 const bool foundHeight = !height.name.isEmpty();
480 if (foundWidth && !foundHeight) {
481 mergedItems.insert(0, width);
482 } else if (foundHeight && !foundWidth) {
483 mergedItems.insert(0, height);
484 } else if (foundWidth && foundHeight) {
485 KLoadMetaDataThread::Item size;
486 size.label = i18nc("@label", "Width x Height:");
487 size.value = width.value + " x " + height.value;
488 mergedItems.insert(0, size);
489 }
490
491 return mergedItems;
492 }
493 #endif
494
495 KMetaDataWidget::KMetaDataWidget(QWidget* parent) :
496 QWidget(parent),
497 d(new Private(this))
498 {
499 }
500
501 KMetaDataWidget::~KMetaDataWidget()
502 {
503 delete d;
504 }
505
506 void KMetaDataWidget::setItem(const KFileItem& item)
507 {
508 // update values for "type", "size", "modified",
509 // "owner" and "permissions" synchronously
510 d->m_sizeLabel->setText(i18nc("@label", "Size:"));
511 if (item.isDir()) {
512 d->m_typeInfo->setText(i18nc("@label", "Folder"));
513 d->setRowVisible(d->m_sizeInfo, false);
514 } else {
515 d->m_typeInfo->setText(item.mimeComment());
516 d->m_sizeInfo->setText(KIO::convertSize(item.size()));
517 d->setRowVisible(d->m_sizeInfo, d->m_sizeVisible);
518 }
519 d->m_modifiedInfo->setText(KGlobal::locale()->formatDateTime(item.time(KFileItem::ModificationTime), KLocale::FancyLongDate));
520 d->m_ownerInfo->setText(item.user());
521 d->m_permissionsInfo->setText(item.permissionsString());
522
523 setItems(KFileItemList() << item);
524 }
525
526 void KMetaDataWidget::setItems(const KFileItemList& items)
527 {
528 d->m_fileItems = items;
529
530 if (items.count() > 1) {
531 // calculate the size of all items and show this
532 // information to the user
533 d->m_sizeLabel->setText(i18nc("@label", "Total Size:"));
534 d->setRowVisible(d->m_sizeInfo, d->m_sizeVisible);
535
536 quint64 totalSize = 0;
537 foreach (const KFileItem& item, items) {
538 if (!item.isDir() && !item.isLink()) {
539 totalSize += item.size();
540 }
541 }
542 d->m_sizeInfo->setText(KIO::convertSize(totalSize));
543 }
544
545 #ifdef HAVE_NEPOMUK
546 if (d->m_nepomukActivated) {
547 QList<KUrl> urls;
548 foreach (const KFileItem& item, items) {
549 const KUrl url = item.nepomukUri();
550 if (url.isValid()) {
551 urls.append(url);
552 }
553 }
554
555 if (d->m_loadMetaDataThread != 0) {
556 disconnect(d->m_loadMetaDataThread, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
557 d->m_loadMetaDataThread->cancelAndDelete();
558 }
559
560 d->m_loadMetaDataThread = new KLoadMetaDataThread();
561 connect(d->m_loadMetaDataThread, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
562 d->m_loadMetaDataThread->load(urls);
563 }
564 #endif
565 }
566
567 void KMetaDataWidget::setItem(const KUrl& url)
568 {
569 KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url);
570 item.refresh();
571 setItem(item);
572 }
573
574 void KMetaDataWidget::setItems(const QList<KUrl>& urls)
575 {
576 KFileItemList items;
577 foreach (const KUrl& url, urls) {
578 KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url);
579 item.refresh();
580 items.append(item);
581 }
582 setItems(items);
583 }
584
585 KFileItemList KMetaDataWidget::items() const
586 {
587 return d->m_fileItems;
588 }
589
590 void KMetaDataWidget::setReadOnly(bool readOnly)
591 {
592 d->m_readOnly = readOnly;
593 #ifdef HAVE_NEPOMUK
594 // TODO: encapsulate this code as part of a metadata-model for KDE 4.5
595 if (d->m_taggingWidget)
596 d->m_taggingWidget->setReadOnly(readOnly);
597 if (d->m_commentWidget)
598 d->m_commentWidget->setReadOnly(readOnly);
599 #endif
600 }
601
602 bool KMetaDataWidget::isReadOnly() const
603 {
604 return d->m_readOnly;
605 }
606
607 void KMetaDataWidget::setVisibleDataTypes(MetaDataTypes data)
608 {
609 d->m_visibleDataTypes = data;
610 d->updateRowsVisibility();
611 }
612
613 KMetaDataWidget::MetaDataTypes KMetaDataWidget::visibleDataTypes() const
614 {
615 return d->m_visibleDataTypes;
616 }
617
618 QSize KMetaDataWidget::sizeHint() const
619 {
620 const int fixedWidth = 200;
621
622 int height = d->m_gridLayout->margin() * 2 +
623 d->m_gridLayout->spacing() * (d->m_rows.count() - 1);
624
625 foreach (const Private::Row& row, d->m_rows) {
626 if (row.infoWidget != 0) {
627 int rowHeight = row.infoWidget->heightForWidth(fixedWidth / 2);
628 if (rowHeight <= 0) {
629 rowHeight = row.infoWidget->sizeHint().height();
630 }
631 height += rowHeight;
632 }
633 }
634
635 return QSize(fixedWidth, height);
636 }
637
638 bool KMetaDataWidget::event(QEvent* event)
639 {
640 if (event->type() == QEvent::Polish) {
641 // The adding of rows is not done in the constructor. This allows the
642 // client of KMetaDataWidget to set a proper foreground role which
643 // will be respected by the rows.
644
645 d->addRow(new QLabel(i18nc("@label", "Type:"), this), d->m_typeInfo);
646 d->addRow(d->m_sizeLabel, d->m_sizeInfo);
647 d->addRow(new QLabel(i18nc("@label", "Modified:"), this), d->m_modifiedInfo);
648 d->addRow(new QLabel(i18nc("@label", "Owner:"), this), d->m_ownerInfo);
649 d->addRow(new QLabel(i18nc("@label", "Permissions:"), this), d->m_permissionsInfo);
650
651 #ifdef HAVE_NEPOMUK
652 if (d->m_nepomukActivated) {
653 d->addRow(new QLabel(i18nc("@label", "Rating:"), this), d->m_ratingWidget);
654 d->addRow(new QLabel(i18nc("@label", "Tags:"), this), d->m_taggingWidget);
655 d->addRow(new QLabel(i18nc("@label", "Comment:"), this), d->m_commentWidget);
656 }
657 #endif
658
659 d->updateRowsVisibility();
660 }
661
662 return QWidget::event(event);
663 }
664
665 #include "kmetadatawidget.moc"