]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/information/kmetadatawidget.cpp
search finetuning:
[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
98 #ifdef HAVE_NEPOMUK
99 /**
100 * Disables the metadata widget and starts the job that
101 * changes the meta data asynchronously. After the job
102 * has been finished, the metadata widget gets enabled again.
103 */
104 void startChangeDataJob(KJob* job);
105
106 /**
107 * Merges items like 'width' and 'height' as one item.
108 */
109 QList<KLoadMetaDataThread::Item> mergedItems(const QList<KLoadMetaDataThread::Item>& items);
110 #endif
111
112 bool m_sizeVisible;
113 bool m_readOnly;
114 MetaDataTypes m_visibleDataTypes;
115 QList<KFileItem> m_fileItems;
116 QList<Row> m_rows;
117
118 QGridLayout* m_gridLayout;
119
120 QLabel* m_typeInfo;
121 QLabel* m_sizeLabel;
122 QLabel* m_sizeInfo;
123 QLabel* m_modifiedInfo;
124 QLabel* m_ownerInfo;
125 QLabel* m_permissionsInfo;
126
127 #ifdef HAVE_NEPOMUK
128 KRatingWidget* m_ratingWidget;
129 KTaggingWidget* m_taggingWidget;
130 KCommentWidget* m_commentWidget;
131
132 QMap<KUrl, Nepomuk::Resource> m_files;
133
134 KLoadMetaDataThread* m_loadMetaDataThread;
135 #endif
136
137 private:
138 KMetaDataWidget* const q;
139 };
140
141 KMetaDataWidget::Private::Private(KMetaDataWidget* parent) :
142 m_sizeVisible(true),
143 m_readOnly(false),
144 m_visibleDataTypes(TypeData | SizeData | ModifiedData | OwnerData |
145 PermissionsData | RatingData | TagsData | CommentData),
146 m_fileItems(),
147 m_rows(),
148 m_gridLayout(0),
149 m_typeInfo(0),
150 m_sizeLabel(0),
151 m_sizeInfo(0),
152 m_modifiedInfo(0),
153 m_ownerInfo(0),
154 m_permissionsInfo(0),
155 #ifdef HAVE_NEPOMUK
156 m_ratingWidget(0),
157 m_taggingWidget(0),
158 m_commentWidget(0),
159 m_files(),
160 m_loadMetaDataThread(0),
161 #endif
162 q(parent)
163 {
164 const QFontMetrics fontMetrics(KGlobalSettings::smallestReadableFont());
165
166 m_gridLayout = new QGridLayout(parent);
167 m_gridLayout->setMargin(0);
168 m_gridLayout->setSpacing(fontMetrics.height() / 4);
169
170 m_typeInfo = new QLabel(parent);
171 m_sizeLabel = new QLabel(parent);
172 m_sizeInfo = new QLabel(parent);
173 m_modifiedInfo = new QLabel(parent);
174 m_ownerInfo = new QLabel(parent);
175 m_permissionsInfo = new QLabel(parent);
176
177 addRow(new QLabel(i18nc("@label", "Type:"), parent), m_typeInfo);
178 addRow(m_sizeLabel, m_sizeInfo);
179 addRow(new QLabel(i18nc("@label", "Modified:"), parent), m_modifiedInfo);
180 addRow(new QLabel(i18nc("@label", "Owner:"), parent), m_ownerInfo);
181 addRow(new QLabel(i18nc("@label", "Permissions:"), parent), m_permissionsInfo);
182
183 #ifdef HAVE_NEPOMUK
184 if (Nepomuk::ResourceManager::instance()->init() == 0) {
185 m_ratingWidget = new KRatingWidget(parent);
186 m_ratingWidget->setFixedHeight(fontMetrics.height());
187 connect(m_ratingWidget, SIGNAL(ratingChanged(unsigned int)),
188 q, SLOT(slotRatingChanged(unsigned int)));
189
190 m_taggingWidget = new KTaggingWidget(parent);
191 connect(m_taggingWidget, SIGNAL(tagsChanged(const QList<Nepomuk::Tag>&)),
192 q, SLOT(slotTagsChanged(const QList<Nepomuk::Tag>&)));
193
194 m_commentWidget = new KCommentWidget(parent);
195 connect(m_commentWidget, SIGNAL(commentChanged(const QString&)),
196 q, SLOT(slotCommentChanged(const QString&)));
197
198 addRow(new QLabel(i18nc("@label", "Rating:"), parent), m_ratingWidget);
199 addRow(new QLabel(i18nc("@label", "Tags:"), parent), m_taggingWidget);
200 addRow(new QLabel(i18nc("@label", "Comment:"), parent), m_commentWidget);
201 }
202 #endif
203
204 initMetaInfoSettings();
205 updateRowsVisibility();
206 }
207
208 KMetaDataWidget::Private::~Private()
209 {
210 #ifdef HAVE_NEPOMUK
211 if (m_loadMetaDataThread != 0) {
212 disconnect(m_loadMetaDataThread, SIGNAL(finished()), q, SLOT(slotLoadingFinished()));
213 m_loadMetaDataThread->cancelAndDelete();
214 m_loadMetaDataThread = 0;
215 }
216 #endif
217 }
218
219 void KMetaDataWidget::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 KMetaDataWidget::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
264 void KMetaDataWidget::Private::initMetaInfoSettings()
265 {
266 KConfig config("kmetainformationrc", KConfig::NoGlobals);
267 KConfigGroup settings = config.group("Show");
268 if (!settings.readEntry("initialized", false)) {
269 // The resource file is read the first time. Assure
270 // that some meta information is disabled per default.
271
272 static const char* disabledProperties[] = {
273 "asText", "contentSize", "created", "depth", "description", "fileExtension",
274 "fileName", "fileSize", "hasTag", "isPartOf", "lastModified", "mimeType", "name",
275 "numericRating", "parentUrl", "permissions", "plainTextContent", "owner",
276 "sourceModified", "url",
277 0 // mandatory last entry
278 };
279
280 int i = 0;
281 while (disabledProperties[i] != 0) {
282 settings.writeEntry(disabledProperties[i], false);
283 ++i;
284 }
285
286 // mark the group as initialized
287 settings.writeEntry("initialized", true);
288 }
289 }
290
291 void KMetaDataWidget::Private::updateRowsVisibility()
292 {
293 KConfig config("kmetainformationrc", KConfig::NoGlobals);
294 KConfigGroup settings = config.group("Show");
295
296 setRowVisible(m_typeInfo,
297 (m_visibleDataTypes & KMetaDataWidget::TypeData) &&
298 settings.readEntry("type", true));
299
300 // Cache in m_sizeVisible whether the size should be shown. This
301 // is necessary as the size is temporary hidden when the target
302 // file item is a directory.
303 m_sizeVisible = (m_visibleDataTypes & KMetaDataWidget::SizeData) &&
304 settings.readEntry("size", true);
305 setRowVisible(m_sizeInfo, m_sizeVisible);
306
307 setRowVisible(m_modifiedInfo,
308 (m_visibleDataTypes & KMetaDataWidget::ModifiedData) &&
309 settings.readEntry("modified", true));
310
311 setRowVisible(m_ownerInfo,
312 (m_visibleDataTypes & KMetaDataWidget::OwnerData) &&
313 settings.readEntry("owner", true));
314
315 setRowVisible(m_permissionsInfo,
316 (m_visibleDataTypes & KMetaDataWidget::PermissionsData) &&
317 settings.readEntry("permissions", true));
318
319 #ifdef HAVE_NEPOMUK
320 if (Nepomuk::ResourceManager::instance()->init() == 0) {
321 setRowVisible(m_ratingWidget,
322 (m_visibleDataTypes & KMetaDataWidget::RatingData) &&
323 settings.readEntry("rating", true));
324
325 setRowVisible(m_taggingWidget,
326 (m_visibleDataTypes & KMetaDataWidget::TagsData) &&
327 settings.readEntry("tags", true));
328
329 setRowVisible(m_commentWidget,
330 (m_visibleDataTypes & KMetaDataWidget::CommentData) &&
331 settings.readEntry("comment", true));
332 }
333 #endif
334 }
335
336 void KMetaDataWidget::Private::slotLoadingFinished()
337 {
338 #ifdef HAVE_NEPOMUK
339 Q_ASSERT(m_loadMetaDataThread != 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 addRow(infoLabel, infoValue);
364 }
365 ++index;
366 }
367 if (items.count() > 0) {
368 --index;
369 }
370
371 // remove rows that are not needed anymore
372 for (int i = m_rows.count() - 1; i >= index; --i) {
373 delete m_rows[i].label;
374 delete m_rows[i].infoWidget;
375 m_rows.pop_back();
376 }
377
378 m_files = m_loadMetaDataThread->files();
379
380 delete m_loadMetaDataThread;
381 m_loadMetaDataThread = 0;
382 #endif
383
384 q->updateGeometry();
385 }
386
387 void KMetaDataWidget::Private::slotRatingChanged(unsigned int rating)
388 {
389 #ifdef HAVE_NEPOMUK
390 Nepomuk::MassUpdateJob* job =
391 Nepomuk::MassUpdateJob::rateResources(m_files.values(), rating);
392 startChangeDataJob(job);
393 #else
394 Q_UNUSED(rating);
395 #endif
396 }
397
398 void KMetaDataWidget::Private::slotTagsChanged(const QList<Nepomuk::Tag>& tags)
399 {
400 #ifdef HAVE_NEPOMUK
401 m_taggingWidget->setTags(tags);
402
403 Nepomuk::MassUpdateJob* job =
404 Nepomuk::MassUpdateJob::tagResources(m_files.values(), tags);
405 startChangeDataJob(job);
406 #else
407 Q_UNUSED(tags);
408 #endif
409 }
410
411 void KMetaDataWidget::Private::slotCommentChanged(const QString& comment)
412 {
413 #ifdef HAVE_NEPOMUK
414 Nepomuk::MassUpdateJob* job =
415 Nepomuk::MassUpdateJob::commentResources(m_files.values(), comment);
416 startChangeDataJob(job);
417 #else
418 Q_UNUSED(comment);
419 #endif
420 }
421
422 void KMetaDataWidget::Private::slotMetaDataUpdateDone()
423 {
424 #ifdef HAVE_NEPOMUK
425 q->setEnabled(true);
426 #endif
427 }
428
429 #ifdef HAVE_NEPOMUK
430 void KMetaDataWidget::Private::startChangeDataJob(KJob* job)
431 {
432 connect(job, SIGNAL(result(KJob*)),
433 q, SLOT(slotMetaDataUpdateDone()));
434 q->setEnabled(false); // no updates during execution
435 job->start();
436 }
437
438 QList<KLoadMetaDataThread::Item>
439 KMetaDataWidget::Private::mergedItems(const QList<KLoadMetaDataThread::Item>& items)
440 {
441 // TODO: Currently only "width" and "height" are merged as "width x height". If
442 // other kind of merges should be done too, a more general approach is required.
443 QList<KLoadMetaDataThread::Item> mergedItems;
444
445 KLoadMetaDataThread::Item width;
446 KLoadMetaDataThread::Item height;
447
448 foreach (const KLoadMetaDataThread::Item& item, items) {
449 if (item.name == "width") {
450 width = item;
451 } else if (item.name == "height") {
452 height = item;
453 } else {
454 // insert the item sorted by the label
455 bool inserted = false;
456 int i = 0;
457 const int count = mergedItems.count();
458 while (!inserted && (i < count)) {
459 if (item.label < mergedItems[i].label) {
460 mergedItems.insert(i, item);
461 inserted = true;
462 }
463 ++i;
464 }
465 if (!inserted) {
466 mergedItems.append(item);
467 }
468 }
469 }
470
471 const bool foundWidth = !width.name.isEmpty();
472 const bool foundHeight = !height.name.isEmpty();
473 if (foundWidth && !foundHeight) {
474 mergedItems.insert(0, width);
475 } else if (foundHeight && !foundWidth) {
476 mergedItems.insert(0, height);
477 } else if (foundWidth && foundHeight) {
478 KLoadMetaDataThread::Item size;
479 size.label = i18nc("@label", "Width x Height:");
480 size.value = width.value + " x " + height.value;
481 mergedItems.insert(0, size);
482 }
483
484 return mergedItems;
485 }
486 #endif
487
488 KMetaDataWidget::KMetaDataWidget(QWidget* parent) :
489 QWidget(parent),
490 d(new Private(this))
491 {
492 }
493
494 KMetaDataWidget::~KMetaDataWidget()
495 {
496 delete d;
497 }
498
499 void KMetaDataWidget::setItem(const KFileItem& item)
500 {
501 // update values for "type", "size", "modified",
502 // "owner" and "permissions" synchronously
503 d->m_sizeLabel->setText(i18nc("@label", "Size:"));
504 if (item.isDir()) {
505 d->m_typeInfo->setText(i18nc("@label", "Folder"));
506 d->setRowVisible(d->m_sizeInfo, false);
507 } else {
508 d->m_typeInfo->setText(item.mimeComment());
509 d->m_sizeInfo->setText(KIO::convertSize(item.size()));
510 d->setRowVisible(d->m_sizeInfo, d->m_sizeVisible);
511 }
512 d->m_modifiedInfo->setText(KGlobal::locale()->formatDateTime(item.time(KFileItem::ModificationTime), KLocale::FancyLongDate));
513 d->m_ownerInfo->setText(item.user());
514 d->m_permissionsInfo->setText(item.permissionsString());
515
516 setItems(KFileItemList() << item);
517 }
518
519 void KMetaDataWidget::setItems(const KFileItemList& items)
520 {
521 d->m_fileItems = items;
522
523 if (items.count() > 1) {
524 // calculate the size of all items and show this
525 // information to the user
526 d->m_sizeLabel->setText(i18nc("@label", "Total Size:"));
527 d->setRowVisible(d->m_sizeInfo, d->m_sizeVisible);
528
529 quint64 totalSize = 0;
530 foreach (const KFileItem& item, items) {
531 if (!item.isDir() && !item.isLink()) {
532 totalSize += item.size();
533 }
534 }
535 d->m_sizeInfo->setText(KIO::convertSize(totalSize));
536 }
537
538 #ifdef HAVE_NEPOMUK
539 if (Nepomuk::ResourceManager::instance()->init() == 0) {
540 QList<KUrl> urls;
541 foreach (const KFileItem& item, items) {
542 const KUrl url = item.nepomukUri();
543 if (url.isValid()) {
544 urls.append(url);
545 }
546 }
547
548 if (d->m_loadMetaDataThread != 0) {
549 disconnect(d->m_loadMetaDataThread, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
550 d->m_loadMetaDataThread->cancelAndDelete();
551 }
552
553 d->m_loadMetaDataThread = new KLoadMetaDataThread();
554 connect(d->m_loadMetaDataThread, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
555 d->m_loadMetaDataThread->load(urls);
556 }
557 #endif
558 }
559
560 void KMetaDataWidget::setItem(const KUrl& url)
561 {
562 KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url);
563 item.refresh();
564 setItem(item);
565 }
566
567 void KMetaDataWidget::setItems(const QList<KUrl>& urls)
568 {
569 KFileItemList items;
570 foreach (const KUrl& url, urls) {
571 KFileItem item(KFileItem::Unknown, KFileItem::Unknown, url);
572 item.refresh();
573 items.append(item);
574 }
575 setItems(items);
576 }
577
578 KFileItemList KMetaDataWidget::items() const
579 {
580 return d->m_fileItems;
581 }
582
583 void KMetaDataWidget::setReadOnly(bool readOnly)
584 {
585 d->m_readOnly = readOnly;
586 #ifdef HAVE_NEPOMUK
587 // TODO: encapsulate this code as part of a metadata-model for KDE 4.5
588 d->m_taggingWidget->setReadOnly(readOnly);
589 d->m_commentWidget->setReadOnly(readOnly);
590 #endif
591 }
592
593 bool KMetaDataWidget::isReadOnly() const
594 {
595 return d->m_readOnly;
596 }
597
598 void KMetaDataWidget::setVisibleDataTypes(MetaDataTypes data)
599 {
600 d->m_visibleDataTypes = data;
601 d->updateRowsVisibility();
602 }
603
604 KMetaDataWidget::MetaDataTypes KMetaDataWidget::visibleDataTypes() const
605 {
606 return d->m_visibleDataTypes;
607 }
608
609 QSize KMetaDataWidget::sizeHint() const
610 {
611 const int fixedWidth = 200;
612
613 int height = d->m_gridLayout->margin() * 2 +
614 d->m_gridLayout->spacing() * (d->m_rows.count() - 1);
615
616 foreach (const Private::Row& row, d->m_rows) {
617 if (row.infoWidget != 0) {
618 int rowHeight = row.infoWidget->heightForWidth(fixedWidth / 2);
619 if (rowHeight <= 0) {
620 rowHeight = row.infoWidget->sizeHint().height();
621 }
622 height += rowHeight;
623 }
624 }
625
626 return QSize(fixedWidth, height);
627 }
628
629 #include "kmetadatawidget.moc"