1 /*****************************************************************************
2 * Copyright (C) 2008 by Sebastian Trueg <trueg@kde.org> *
3 * Copyright (C) 2009 by Peter Penz <peter.penz@gmx.at> *
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. *
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. *
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 *****************************************************************************/
20 #include "kmetadatawidget.h"
23 #include <kconfiggroup.h>
24 #include <kfileitem.h>
25 #include <kglobalsettings.h>
30 #include <QFontMetrics>
31 #include <QGridLayout>
36 #include <config-nepomuk.h>
38 #define DISABLE_NEPOMUK_LEGACY
40 #include "kcommentwidget_p.h"
41 #include "kloadmetadatathread_p.h"
42 #include "ktaggingwidget_p.h"
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"
53 #include <QSpacerItem>
62 class KMetaDataWidget::Private
71 Private(KMetaDataWidget
* parent
);
74 void addRow(QLabel
* label
, QWidget
* infoWidget
);
75 void removeMetaInfoRows();
76 void setRowVisible(QWidget
* infoWidget
, bool visible
);
79 * Initializes the configuration file "kmetainformationrc"
80 * with proper default settings for the first start in
81 * an uninitialized environment.
83 void initMetaInfoSettings();
86 * Parses the configuration file "kmetainformationrc" and
87 * updates the visibility of all rows.
89 void updateRowsVisibility();
91 void slotLoadingFinished();
93 void slotRatingChanged(unsigned int rating
);
94 void slotTagsChanged(const QList
<Nepomuk::Tag
>& tags
);
95 void slotCommentChanged(const QString
& comment
);
97 void slotMetaDataUpdateDone();
98 void slotLinkActivated(const QString
& link
);
100 void slotTagActivated(const Nepomuk::Tag
& tag
);
104 * Disables the metadata widget and starts the job that
105 * changes the meta data asynchronously. After the job
106 * has been finished, the metadata widget gets enabled again.
108 void startChangeDataJob(KJob
* job
);
111 * Merges items like 'width' and 'height' as one item.
113 QList
<KLoadMetaDataThread::Item
> mergedItems(const QList
<KLoadMetaDataThread::Item
>& items
);
118 bool m_nepomukActivated
;
120 MetaDataTypes m_visibleDataTypes
;
121 QList
<KFileItem
> m_fileItems
;
124 QGridLayout
* m_gridLayout
;
129 QLabel
* m_modifiedInfo
;
131 QLabel
* m_permissionsInfo
;
134 KRatingWidget
* m_ratingWidget
;
135 KTaggingWidget
* m_taggingWidget
;
136 KCommentWidget
* m_commentWidget
;
138 QMap
<KUrl
, Nepomuk::Resource
> m_files
;
140 KLoadMetaDataThread
* m_loadMetaDataThread
;
144 KMetaDataWidget
* const q
;
147 KMetaDataWidget::Private::Private(KMetaDataWidget
* parent
) :
150 m_nepomukActivated(false),
152 m_visibleDataTypes(TypeData
| SizeData
| ModifiedData
| OwnerData
|
153 PermissionsData
| RatingData
| TagsData
| CommentData
),
162 m_permissionsInfo(0),
168 m_loadMetaDataThread(0),
172 const QFontMetrics
fontMetrics(KGlobalSettings::smallestReadableFont());
174 m_gridLayout
= new QGridLayout(parent
);
175 m_gridLayout
->setMargin(0);
176 m_gridLayout
->setSpacing(fontMetrics
.height() / 4);
178 m_typeInfo
= new QLabel(parent
);
179 m_sizeLabel
= new QLabel(parent
);
180 m_sizeInfo
= new QLabel(parent
);
181 m_modifiedInfo
= new QLabel(parent
);
182 m_ownerInfo
= new QLabel(parent
);
183 m_permissionsInfo
= new QLabel(parent
);
186 m_nepomukActivated
= (Nepomuk::ResourceManager::instance()->init() == 0);
187 if (m_nepomukActivated
) {
188 m_ratingWidget
= new KRatingWidget(parent
);
189 m_ratingWidget
->setFixedHeight(fontMetrics
.height());
190 const Qt::Alignment align
= (parent
->layoutDirection() == Qt::LeftToRight
) ?
191 Qt::AlignLeft
: Qt::AlignRight
;
192 m_ratingWidget
->setAlignment(align
);
193 connect(m_ratingWidget
, SIGNAL(ratingChanged(unsigned int)),
194 q
, SLOT(slotRatingChanged(unsigned int)));
196 m_taggingWidget
= new KTaggingWidget(parent
);
197 connect(m_taggingWidget
, SIGNAL(tagsChanged(const QList
<Nepomuk::Tag
>&)),
198 q
, SLOT(slotTagsChanged(const QList
<Nepomuk::Tag
>&)));
199 connect(m_taggingWidget
, SIGNAL(tagActivated(const Nepomuk::Tag
&)),
200 q
, SLOT(slotTagActivated(const Nepomuk::Tag
&)));
202 m_commentWidget
= new KCommentWidget(parent
);
203 connect(m_commentWidget
, SIGNAL(commentChanged(const QString
&)),
204 q
, SLOT(slotCommentChanged(const QString
&)));
208 initMetaInfoSettings();
211 KMetaDataWidget::Private::~Private()
214 if (m_loadMetaDataThread
!= 0) {
215 disconnect(m_loadMetaDataThread
, SIGNAL(finished()), q
, SLOT(slotLoadingFinished()));
216 m_loadMetaDataThread
->cancelAndDelete();
217 m_loadMetaDataThread
= 0;
222 void KMetaDataWidget::Private::addRow(QLabel
* label
, QWidget
* infoWidget
)
226 row
.infoWidget
= infoWidget
;
229 const QFont smallFont
= KGlobalSettings::smallestReadableFont();
230 // use a brighter color for the label and a small font size
231 QPalette palette
= label
->palette();
232 const QPalette::ColorRole role
= q
->foregroundRole();
233 QColor textColor
= palette
.color(role
);
234 textColor
.setAlpha(128);
235 palette
.setColor(role
, textColor
);
236 label
->setPalette(palette
);
237 label
->setForegroundRole(role
);
238 label
->setFont(smallFont
);
239 label
->setWordWrap(true);
240 label
->setAlignment(Qt::AlignTop
| Qt::AlignRight
);
242 infoWidget
->setForegroundRole(role
);
243 QLabel
* infoLabel
= qobject_cast
<QLabel
*>(infoWidget
);
244 if (infoLabel
!= 0) {
245 infoLabel
->setFont(smallFont
);
246 infoLabel
->setWordWrap(true);
247 infoLabel
->setAlignment(Qt::AlignTop
| Qt::AlignLeft
);
250 // add the row to grid layout
251 const int rowIndex
= m_rows
.count();
252 m_gridLayout
->addWidget(label
, rowIndex
, 0, Qt::AlignRight
);
253 const int spacerWidth
= QFontMetrics(smallFont
).size(Qt::TextSingleLine
, " ").width();
254 m_gridLayout
->addItem(new QSpacerItem(spacerWidth
, 1), rowIndex
, 1);
255 m_gridLayout
->addWidget(infoWidget
, rowIndex
, 2, Qt::AlignLeft
);
258 void KMetaDataWidget::Private::setRowVisible(QWidget
* infoWidget
, bool visible
)
260 foreach (const Row
& row
, m_rows
) {
261 if (row
.infoWidget
== infoWidget
) {
262 row
.label
->setVisible(visible
);
263 row
.infoWidget
->setVisible(visible
);
270 void KMetaDataWidget::Private::initMetaInfoSettings()
272 const int currentVersion
= 2; // increase version, if the blacklist of disabled
273 // properties should be updated
275 KConfig
config("kmetainformationrc", KConfig::NoGlobals
);
276 if (config
.group("Misc").readEntry("version", 0) < currentVersion
) {
277 // The resource file is read the first time. Assure
278 // that some meta information is disabled per default.
281 config
.deleteGroup( "Show" );
282 KConfigGroup settings
= config
.group("Show");
284 // trueg: KDE 4.5: use a blacklist of actual rdf properties
286 static const char* const disabledProperties
[] = {
287 "asText", "contentSize", "created", "depth", "description", "fileExtension",
288 "fileName", "fileSize", "hasTag", "lastModified", "mimeType", "name",
289 "numericRating", "parentUrl", "permissions", "plainTextContent", "owner",
290 "sourceModified", "url",
291 0 // mandatory last entry
294 for (int i
= 0; disabledProperties
[i
] != 0; ++i
) {
295 settings
.writeEntry(disabledProperties
[i
], false);
298 // mark the group as initialized
299 config
.group("Misc").writeEntry("version", currentVersion
);
303 void KMetaDataWidget::Private::updateRowsVisibility()
305 KConfig
config("kmetainformationrc", KConfig::NoGlobals
);
306 KConfigGroup settings
= config
.group("Show");
308 setRowVisible(m_typeInfo
,
309 (m_visibleDataTypes
& KMetaDataWidget::TypeData
) &&
310 settings
.readEntry("type", true));
312 // Cache in m_sizeVisible whether the size should be shown. This
313 // is necessary as the size is temporary hidden when the target
314 // file item is a directory.
315 m_sizeVisible
= (m_visibleDataTypes
& KMetaDataWidget::SizeData
) &&
316 settings
.readEntry("size", true);
317 bool visible
= m_sizeVisible
;
318 if (visible
&& (m_fileItems
.count() == 1)) {
319 // don't show the size information, if one directory is shown
320 const KFileItem item
= m_fileItems
.first();
321 visible
= !item
.isNull() && !item
.isDir();
323 setRowVisible(m_sizeInfo
, visible
);
325 setRowVisible(m_modifiedInfo
,
326 (m_visibleDataTypes
& KMetaDataWidget::ModifiedData
) &&
327 settings
.readEntry("modified", true));
329 setRowVisible(m_ownerInfo
,
330 (m_visibleDataTypes
& KMetaDataWidget::OwnerData
) &&
331 settings
.readEntry("owner", true));
333 setRowVisible(m_permissionsInfo
,
334 (m_visibleDataTypes
& KMetaDataWidget::PermissionsData
) &&
335 settings
.readEntry("permissions", true));
338 if (m_nepomukActivated
) {
339 setRowVisible(m_ratingWidget
,
340 (m_visibleDataTypes
& KMetaDataWidget::RatingData
) &&
341 settings
.readEntry("rating", true));
343 setRowVisible(m_taggingWidget
,
344 (m_visibleDataTypes
& KMetaDataWidget::TagsData
) &&
345 settings
.readEntry("tags", true));
347 setRowVisible(m_commentWidget
,
348 (m_visibleDataTypes
& KMetaDataWidget::CommentData
) &&
349 settings
.readEntry("comment", true));
354 void KMetaDataWidget::Private::slotLoadingFinished()
357 if (m_loadMetaDataThread
== 0) {
358 // The signal finished() has been emitted, but the thread has been marked
359 // as invalid in the meantime. Just ignore the signal in this case.
363 if (m_nepomukActivated
) {
364 Q_ASSERT(m_ratingWidget
!= 0);
365 Q_ASSERT(m_commentWidget
!= 0);
366 Q_ASSERT(m_taggingWidget
!= 0);
367 m_ratingWidget
->setRating(m_loadMetaDataThread
->rating());
368 m_commentWidget
->setText(m_loadMetaDataThread
->comment());
369 m_taggingWidget
->setTags(m_loadMetaDataThread
->tags());
372 // Show the remaining meta information as text. The number
373 // of required rows may very. Existing rows are reused to
374 // prevent flickering.
375 int rowIndex
= m_fixedRowCount
;
376 const QList
<KLoadMetaDataThread::Item
> items
= mergedItems(m_loadMetaDataThread
->items());
377 foreach (const KLoadMetaDataThread::Item
& item
, items
) {
378 const QString itemLabel
= item
.label
;
379 QString itemValue
= item
.value
;
380 if (item
.value
.startsWith("<a href=")) {
381 // use the text color for the value-links, to create a visual difference
382 // to the semantically different links like "Change..."
383 const QColor linkColor
= q
->palette().text().color();
386 decoration
= QString::fromLatin1("text-decoration:none;");
388 const QString styleText
= QString::fromLatin1("style=\"color:%1;%2\" ")
389 .arg(linkColor
.name())
391 itemValue
.insert(3 /* after "<a "*/, styleText
);
393 if (rowIndex
< m_rows
.count()) {
394 // adjust texts of the current row
395 m_rows
[rowIndex
].label
->setText(itemLabel
);
396 QLabel
* infoValueLabel
= qobject_cast
<QLabel
*>(m_rows
[rowIndex
].infoWidget
);
397 Q_ASSERT(infoValueLabel
!= 0);
398 infoValueLabel
->setText(itemValue
);
401 QLabel
* infoLabel
= new QLabel(itemLabel
, q
);
402 QLabel
* infoValue
= new QLabel(itemValue
, q
);
403 connect(infoValue
, SIGNAL(linkActivated(QString
)),
404 q
, SLOT(slotLinkActivated(QString
)));
405 addRow(infoLabel
, infoValue
);
410 // remove rows that are not needed anymore
411 for (int i
= m_rows
.count() - 1; i
>= rowIndex
; --i
) {
412 delete m_rows
[i
].label
;
413 delete m_rows
[i
].infoWidget
;
417 m_files
= m_loadMetaDataThread
->files();
419 m_loadMetaDataThread
->deleteLater();
420 m_loadMetaDataThread
= 0;
426 void KMetaDataWidget::Private::slotRatingChanged(unsigned int rating
)
429 Nepomuk::MassUpdateJob
* job
=
430 Nepomuk::MassUpdateJob::rateResources(m_files
.values(), rating
);
431 startChangeDataJob(job
);
437 void KMetaDataWidget::Private::slotTagsChanged(const QList
<Nepomuk::Tag
>& tags
)
440 m_taggingWidget
->setTags(tags
);
442 Nepomuk::MassUpdateJob
* job
=
443 Nepomuk::MassUpdateJob::tagResources(m_files
.values(), tags
);
444 startChangeDataJob(job
);
450 void KMetaDataWidget::Private::slotCommentChanged(const QString
& comment
)
453 Nepomuk::MassUpdateJob
* job
=
454 Nepomuk::MassUpdateJob::commentResources(m_files
.values(), comment
);
455 startChangeDataJob(job
);
461 void KMetaDataWidget::Private::slotTagActivated(const Nepomuk::Tag
& tag
)
464 emit q
->urlActivated(tag
.resourceUri());
470 void KMetaDataWidget::Private::slotMetaDataUpdateDone()
477 void KMetaDataWidget::Private::slotLinkActivated(const QString
& link
)
479 emit q
->urlActivated(KUrl(link
));
483 void KMetaDataWidget::Private::startChangeDataJob(KJob
* job
)
485 connect(job
, SIGNAL(result(KJob
*)),
486 q
, SLOT(slotMetaDataUpdateDone()));
487 q
->setEnabled(false); // no updates during execution
491 QList
<KLoadMetaDataThread::Item
>
492 KMetaDataWidget::Private::mergedItems(const QList
<KLoadMetaDataThread::Item
>& items
)
494 // TODO: Currently only "width" and "height" are merged as "width x height". If
495 // other kind of merges should be done too, a more general approach is required.
496 QList
<KLoadMetaDataThread::Item
> mergedItems
;
498 KLoadMetaDataThread::Item width
;
499 KLoadMetaDataThread::Item height
;
501 foreach (const KLoadMetaDataThread::Item
& item
, items
) {
502 if (item
.name
== "width") {
504 } else if (item
.name
== "height") {
507 // insert the item sorted by the label
509 while ( mergedItems
.count() > pos
&&
510 mergedItems
[pos
].label
< item
.label
) {
513 mergedItems
.insert( pos
, item
);
517 const bool foundWidth
= !width
.name
.isEmpty();
518 const bool foundHeight
= !height
.name
.isEmpty();
519 if (foundWidth
&& !foundHeight
) {
520 mergedItems
.insert(0, width
);
521 } else if (foundHeight
&& !foundWidth
) {
522 mergedItems
.insert(0, height
);
523 } else if (foundWidth
&& foundHeight
) {
524 KLoadMetaDataThread::Item size
;
525 size
.label
= i18nc("@label", "Width x Height:");
526 size
.value
= width
.value
+ " x " + height
.value
;
527 mergedItems
.insert(0, size
);
534 KMetaDataWidget::KMetaDataWidget(QWidget
* parent
) :
540 KMetaDataWidget::~KMetaDataWidget()
545 void KMetaDataWidget::setItem(const KFileItem
& item
)
547 // update values for "type", "size", "modified",
548 // "owner" and "permissions" synchronously
549 d
->m_sizeLabel
->setText(i18nc("@label", "Size:"));
551 d
->m_typeInfo
->setText(i18nc("@label", "Folder"));
552 d
->setRowVisible(d
->m_sizeInfo
, false);
554 d
->m_typeInfo
->setText(item
.mimeComment());
555 d
->m_sizeInfo
->setText(KIO::convertSize(item
.size()));
556 d
->setRowVisible(d
->m_sizeInfo
, d
->m_sizeVisible
);
558 d
->m_modifiedInfo
->setText(KGlobal::locale()->formatDateTime(item
.time(KFileItem::ModificationTime
), KLocale::FancyLongDate
));
559 d
->m_ownerInfo
->setText(item
.user());
560 d
->m_permissionsInfo
->setText(item
.permissionsString());
562 setItems(KFileItemList() << item
);
565 void KMetaDataWidget::setItems(const KFileItemList
& items
)
567 d
->m_fileItems
= items
;
569 if (items
.count() > 1) {
570 // calculate the size of all items and show this
571 // information to the user
572 d
->m_sizeLabel
->setText(i18nc("@label", "Total Size:"));
573 d
->setRowVisible(d
->m_sizeInfo
, d
->m_sizeVisible
);
575 quint64 totalSize
= 0;
576 foreach (const KFileItem
& item
, items
) {
577 if (!item
.isDir() && !item
.isLink()) {
578 totalSize
+= item
.size();
581 d
->m_sizeInfo
->setText(KIO::convertSize(totalSize
));
586 foreach (const KFileItem
& item
, items
) {
587 const KUrl url
= item
.nepomukUri();
593 if (d
->m_loadMetaDataThread
!= 0) {
594 disconnect(d
->m_loadMetaDataThread
, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
595 d
->m_loadMetaDataThread
->cancelAndDelete();
598 d
->m_loadMetaDataThread
= new KLoadMetaDataThread();
599 connect(d
->m_loadMetaDataThread
, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
600 d
->m_loadMetaDataThread
->load(urls
);
604 void KMetaDataWidget::setItem(const KUrl
& url
)
606 KFileItem
item(KFileItem::Unknown
, KFileItem::Unknown
, url
);
611 void KMetaDataWidget::setItems(const QList
<KUrl
>& urls
)
614 foreach (const KUrl
& url
, urls
) {
615 KFileItem
item(KFileItem::Unknown
, KFileItem::Unknown
, url
);
622 KFileItemList
KMetaDataWidget::items() const
624 return d
->m_fileItems
;
627 void KMetaDataWidget::setReadOnly(bool readOnly
)
629 d
->m_readOnly
= readOnly
;
631 // TODO: encapsulate this code as part of a metadata-model for KDE 4.5
632 if (d
->m_taggingWidget
)
633 d
->m_taggingWidget
->setReadOnly(readOnly
);
634 if (d
->m_commentWidget
)
635 d
->m_commentWidget
->setReadOnly(readOnly
);
639 bool KMetaDataWidget::isReadOnly() const
641 return d
->m_readOnly
;
644 void KMetaDataWidget::setVisibleDataTypes(MetaDataTypes data
)
646 d
->m_visibleDataTypes
= data
;
647 d
->updateRowsVisibility();
650 KMetaDataWidget::MetaDataTypes
KMetaDataWidget::visibleDataTypes() const
652 return d
->m_visibleDataTypes
;
655 QSize
KMetaDataWidget::sizeHint() const
657 const int fixedWidth
= 200;
659 int height
= d
->m_gridLayout
->margin() * 2 +
660 d
->m_gridLayout
->spacing() * (d
->m_rows
.count() - 1);
662 foreach (const Private::Row
& row
, d
->m_rows
) {
663 if (row
.infoWidget
!= 0) {
664 int rowHeight
= row
.infoWidget
->heightForWidth(fixedWidth
/ 2);
665 if (rowHeight
<= 0) {
666 rowHeight
= row
.infoWidget
->sizeHint().height();
672 return QSize(fixedWidth
, height
);
675 bool KMetaDataWidget::event(QEvent
* event
)
677 if (event
->type() == QEvent::Polish
) {
678 // The adding of rows is not done in the constructor. This allows the
679 // client of KMetaDataWidget to set a proper foreground role which
680 // will be respected by the rows.
682 d
->addRow(new QLabel(i18nc("@label", "Type:"), this), d
->m_typeInfo
);
683 d
->addRow(d
->m_sizeLabel
, d
->m_sizeInfo
);
684 d
->addRow(new QLabel(i18nc("@label", "Modified:"), this), d
->m_modifiedInfo
);
685 d
->addRow(new QLabel(i18nc("@label", "Owner:"), this), d
->m_ownerInfo
);
686 d
->addRow(new QLabel(i18nc("@label", "Permissions:"), this), d
->m_permissionsInfo
);
689 if (d
->m_nepomukActivated
) {
690 d
->addRow(new QLabel(i18nc("@label", "Rating:"), this), d
->m_ratingWidget
);
691 d
->addRow(new QLabel(i18nc("@label", "Tags:"), this), d
->m_taggingWidget
);
692 d
->addRow(new QLabel(i18nc("@label", "Comment:"), this), d
->m_commentWidget
);
696 // The current number of rows represents meta data, that will be shown for
697 // all files. Dynamic meta data will be appended after those rows (see
698 // slotLoadingFinished()).
699 d
->m_fixedRowCount
= d
->m_rows
.count();
701 d
->updateRowsVisibility();
704 return QWidget::event(event
);
707 #include "kmetadatawidget.moc"