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