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
;
119 MetaDataTypes m_visibleDataTypes
;
120 QList
<KFileItem
> m_fileItems
;
123 QGridLayout
* m_gridLayout
;
128 QLabel
* m_modifiedInfo
;
130 QLabel
* m_permissionsInfo
;
133 KRatingWidget
* m_ratingWidget
;
134 KTaggingWidget
* m_taggingWidget
;
135 KCommentWidget
* m_commentWidget
;
137 QMap
<KUrl
, Nepomuk::Resource
> m_files
;
139 KLoadMetaDataThread
* m_loadMetaDataThread
;
143 KMetaDataWidget
* const q
;
146 KMetaDataWidget::Private::Private(KMetaDataWidget
* parent
) :
149 m_nepomukActivated(false),
150 m_visibleDataTypes(TypeData
| SizeData
| ModifiedData
| OwnerData
|
151 PermissionsData
| RatingData
| TagsData
| CommentData
),
160 m_permissionsInfo(0),
166 m_loadMetaDataThread(0),
170 const QFontMetrics
fontMetrics(KGlobalSettings::smallestReadableFont());
172 m_gridLayout
= new QGridLayout(parent
);
173 m_gridLayout
->setMargin(0);
174 m_gridLayout
->setSpacing(fontMetrics
.height() / 4);
176 m_typeInfo
= new QLabel(parent
);
177 m_sizeLabel
= new QLabel(parent
);
178 m_sizeInfo
= new QLabel(parent
);
179 m_modifiedInfo
= new QLabel(parent
);
180 m_ownerInfo
= new QLabel(parent
);
181 m_permissionsInfo
= new QLabel(parent
);
184 m_nepomukActivated
= (Nepomuk::ResourceManager::instance()->init() == 0);
185 if (m_nepomukActivated
) {
186 m_ratingWidget
= new KRatingWidget(parent
);
187 m_ratingWidget
->setFixedHeight(fontMetrics
.height());
188 const Qt::Alignment align
= (parent
->layoutDirection() == Qt::LeftToRight
) ?
189 Qt::AlignLeft
: Qt::AlignRight
;
190 m_ratingWidget
->setAlignment(align
);
191 connect(m_ratingWidget
, SIGNAL(ratingChanged(unsigned int)),
192 q
, SLOT(slotRatingChanged(unsigned int)));
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 connect(m_taggingWidget
, SIGNAL(tagActivated(const Nepomuk::Tag
&)),
198 q
, SLOT(slotTagActivated(const Nepomuk::Tag
&)));
200 m_commentWidget
= new KCommentWidget(parent
);
201 connect(m_commentWidget
, SIGNAL(commentChanged(const QString
&)),
202 q
, SLOT(slotCommentChanged(const QString
&)));
206 initMetaInfoSettings();
209 KMetaDataWidget::Private::~Private()
212 if (m_loadMetaDataThread
!= 0) {
213 disconnect(m_loadMetaDataThread
, SIGNAL(finished()), q
, SLOT(slotLoadingFinished()));
214 m_loadMetaDataThread
->cancelAndDelete();
215 m_loadMetaDataThread
= 0;
220 void KMetaDataWidget::Private::addRow(QLabel
* label
, QWidget
* infoWidget
)
224 row
.infoWidget
= infoWidget
;
227 const QFont smallFont
= KGlobalSettings::smallestReadableFont();
228 // use a brighter color for the label and a small font size
229 QPalette palette
= label
->palette();
230 const QPalette::ColorRole role
= q
->foregroundRole();
231 QColor textColor
= palette
.color(role
);
232 textColor
.setAlpha(128);
233 palette
.setColor(role
, textColor
);
234 label
->setPalette(palette
);
235 label
->setForegroundRole(role
);
236 label
->setFont(smallFont
);
237 label
->setWordWrap(true);
238 label
->setAlignment(Qt::AlignTop
| Qt::AlignRight
);
240 infoWidget
->setForegroundRole(role
);
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
);
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
);
256 void KMetaDataWidget::Private::setRowVisible(QWidget
* infoWidget
, bool visible
)
258 foreach (const Row
& row
, m_rows
) {
259 if (row
.infoWidget
== infoWidget
) {
260 row
.label
->setVisible(visible
);
261 row
.infoWidget
->setVisible(visible
);
268 void KMetaDataWidget::Private::initMetaInfoSettings()
270 const int currentVersion
= 1; // increase version, if the blacklist of disabled
271 // properties should be updated
273 KConfig
config("kmetainformationrc", KConfig::NoGlobals
);
274 if (config
.group("Misc").readEntry("version", 0) < currentVersion
) {
275 // The resource file is read the first time. Assure
276 // that some meta information is disabled per default.
279 config
.deleteGroup( "Show" );
280 KConfigGroup settings
= config
.group("Show");
282 // trueg: KDE 4.5: use a blacklist of actual rdf properties
284 static const char* disabledProperties
[] = {
285 "asText", "contentSize", "created", "depth", "description", "fileExtension",
286 "fileName", "fileSize", "hasTag", "isPartOf", "lastModified", "mimeType", "name",
287 "numericRating", "parentUrl", "permissions", "plainTextContent", "owner",
288 "sourceModified", "url",
289 0 // mandatory last entry
292 for (int i
= 0; disabledProperties
[i
] != 0; ++i
) {
293 settings
.writeEntry(disabledProperties
[i
], false);
296 // mark the group as initialized
297 config
.group("Misc").writeEntry("version", currentVersion
);
301 void KMetaDataWidget::Private::updateRowsVisibility()
303 KConfig
config("kmetainformationrc", KConfig::NoGlobals
);
304 KConfigGroup settings
= config
.group("Show");
306 setRowVisible(m_typeInfo
,
307 (m_visibleDataTypes
& KMetaDataWidget::TypeData
) &&
308 settings
.readEntry("type", true));
310 // Cache in m_sizeVisible whether the size should be shown. This
311 // is necessary as the size is temporary hidden when the target
312 // file item is a directory.
313 m_sizeVisible
= (m_visibleDataTypes
& KMetaDataWidget::SizeData
) &&
314 settings
.readEntry("size", true);
315 bool visible
= m_sizeVisible
;
316 if (visible
&& (m_fileItems
.count() == 1)) {
317 // don't show the size information, if one directory is shown
318 const KFileItem item
= m_fileItems
.first();
319 visible
= !item
.isNull() && !item
.isDir();
321 setRowVisible(m_sizeInfo
, visible
);
323 setRowVisible(m_modifiedInfo
,
324 (m_visibleDataTypes
& KMetaDataWidget::ModifiedData
) &&
325 settings
.readEntry("modified", true));
327 setRowVisible(m_ownerInfo
,
328 (m_visibleDataTypes
& KMetaDataWidget::OwnerData
) &&
329 settings
.readEntry("owner", true));
331 setRowVisible(m_permissionsInfo
,
332 (m_visibleDataTypes
& KMetaDataWidget::PermissionsData
) &&
333 settings
.readEntry("permissions", true));
336 if (m_nepomukActivated
) {
337 setRowVisible(m_ratingWidget
,
338 (m_visibleDataTypes
& KMetaDataWidget::RatingData
) &&
339 settings
.readEntry("rating", true));
341 setRowVisible(m_taggingWidget
,
342 (m_visibleDataTypes
& KMetaDataWidget::TagsData
) &&
343 settings
.readEntry("tags", true));
345 setRowVisible(m_commentWidget
,
346 (m_visibleDataTypes
& KMetaDataWidget::CommentData
) &&
347 settings
.readEntry("comment", true));
352 void KMetaDataWidget::Private::slotLoadingFinished()
355 if (m_loadMetaDataThread
== 0) {
356 // The signal finished() has been emitted, but the thread has been marked
357 // as invalid in the meantime. Just ignore the signal in this case.
361 Q_ASSERT(m_ratingWidget
!= 0);
362 Q_ASSERT(m_commentWidget
!= 0);
363 Q_ASSERT(m_taggingWidget
!= 0);
364 m_ratingWidget
->setRating(m_loadMetaDataThread
->rating());
365 m_commentWidget
->setText(m_loadMetaDataThread
->comment());
366 m_taggingWidget
->setTags(m_loadMetaDataThread
->tags());
368 // Show the remaining meta information as text. The number
369 // of required rows may very. Existing rows are reused to
370 // prevent flickering.
371 int index
= 8; // TODO: don't hardcode this value here
372 const int rowCount
= m_rows
.count();
373 Q_ASSERT(rowCount
>= index
);
375 const QList
<KLoadMetaDataThread::Item
> items
= mergedItems(m_loadMetaDataThread
->items());
376 foreach (const KLoadMetaDataThread::Item
& item
, items
) {
377 const QString itemLabel
= item
.label
;
378 QString itemValue
= item
.value
;
379 if (item
.value
.startsWith("<a href=")) {
380 // use the text color for the value-links, to create a visual difference
381 // to the semantically different links like "Change..."
382 const QColor linkColor
= q
->palette().text().color();
385 decoration
= QString::fromLatin1("text-decoration:none;");
387 const QString styleText
= QString::fromLatin1("style=\"color:%1;%2\" ")
388 .arg(linkColor
.name())
390 itemValue
.insert(3 /* after "<a "*/, styleText
);
392 if (index
< rowCount
) {
393 // adjust texts of the current row
394 m_rows
[index
].label
->setText(itemLabel
);
395 QLabel
* infoValueLabel
= qobject_cast
<QLabel
*>(m_rows
[index
].infoWidget
);
396 Q_ASSERT(infoValueLabel
!= 0);
397 infoValueLabel
->setText(itemValue
);
400 QLabel
* infoLabel
= new QLabel(itemLabel
, q
);
401 QLabel
* infoValue
= new QLabel(itemValue
, q
);
402 connect(infoValue
, SIGNAL(linkActivated(QString
)),
403 q
, SLOT(slotLinkActivated(QString
)));
404 addRow(infoLabel
, infoValue
);
408 if (items
.count() > 0) {
412 // remove rows that are not needed anymore
413 for (int i
= m_rows
.count() - 1; i
>= index
; --i
) {
414 delete m_rows
[i
].label
;
415 delete m_rows
[i
].infoWidget
;
419 m_files
= m_loadMetaDataThread
->files();
421 delete m_loadMetaDataThread
;
422 m_loadMetaDataThread
= 0;
428 void KMetaDataWidget::Private::slotRatingChanged(unsigned int rating
)
431 Nepomuk::MassUpdateJob
* job
=
432 Nepomuk::MassUpdateJob::rateResources(m_files
.values(), rating
);
433 startChangeDataJob(job
);
439 void KMetaDataWidget::Private::slotTagsChanged(const QList
<Nepomuk::Tag
>& tags
)
442 m_taggingWidget
->setTags(tags
);
444 Nepomuk::MassUpdateJob
* job
=
445 Nepomuk::MassUpdateJob::tagResources(m_files
.values(), tags
);
446 startChangeDataJob(job
);
452 void KMetaDataWidget::Private::slotCommentChanged(const QString
& comment
)
455 Nepomuk::MassUpdateJob
* job
=
456 Nepomuk::MassUpdateJob::commentResources(m_files
.values(), comment
);
457 startChangeDataJob(job
);
463 void KMetaDataWidget::Private::slotTagActivated(const Nepomuk::Tag
& tag
)
466 emit q
->urlActivated(tag
.resourceUri());
472 void KMetaDataWidget::Private::slotMetaDataUpdateDone()
479 void KMetaDataWidget::Private::slotLinkActivated(const QString
& link
)
481 emit q
->urlActivated(KUrl(link
));
485 void KMetaDataWidget::Private::startChangeDataJob(KJob
* job
)
487 connect(job
, SIGNAL(result(KJob
*)),
488 q
, SLOT(slotMetaDataUpdateDone()));
489 q
->setEnabled(false); // no updates during execution
493 QList
<KLoadMetaDataThread::Item
>
494 KMetaDataWidget::Private::mergedItems(const QList
<KLoadMetaDataThread::Item
>& items
)
496 // TODO: Currently only "width" and "height" are merged as "width x height". If
497 // other kind of merges should be done too, a more general approach is required.
498 QList
<KLoadMetaDataThread::Item
> mergedItems
;
500 KLoadMetaDataThread::Item width
;
501 KLoadMetaDataThread::Item height
;
503 foreach (const KLoadMetaDataThread::Item
& item
, items
) {
504 if (item
.name
== "width") {
506 } else if (item
.name
== "height") {
509 // insert the item sorted by the label
510 bool inserted
= false;
512 const int count
= mergedItems
.count();
513 while (!inserted
&& (i
< count
)) {
514 if (item
.label
< mergedItems
[i
].label
) {
515 mergedItems
.insert(i
, item
);
521 mergedItems
.append(item
);
526 const bool foundWidth
= !width
.name
.isEmpty();
527 const bool foundHeight
= !height
.name
.isEmpty();
528 if (foundWidth
&& !foundHeight
) {
529 mergedItems
.insert(0, width
);
530 } else if (foundHeight
&& !foundWidth
) {
531 mergedItems
.insert(0, height
);
532 } else if (foundWidth
&& foundHeight
) {
533 KLoadMetaDataThread::Item size
;
534 size
.label
= i18nc("@label", "Width x Height:");
535 size
.value
= width
.value
+ " x " + height
.value
;
536 mergedItems
.insert(0, size
);
543 KMetaDataWidget::KMetaDataWidget(QWidget
* parent
) :
549 KMetaDataWidget::~KMetaDataWidget()
554 void KMetaDataWidget::setItem(const KFileItem
& item
)
556 // update values for "type", "size", "modified",
557 // "owner" and "permissions" synchronously
558 d
->m_sizeLabel
->setText(i18nc("@label", "Size:"));
560 d
->m_typeInfo
->setText(i18nc("@label", "Folder"));
561 d
->setRowVisible(d
->m_sizeInfo
, false);
563 d
->m_typeInfo
->setText(item
.mimeComment());
564 d
->m_sizeInfo
->setText(KIO::convertSize(item
.size()));
565 d
->setRowVisible(d
->m_sizeInfo
, d
->m_sizeVisible
);
567 d
->m_modifiedInfo
->setText(KGlobal::locale()->formatDateTime(item
.time(KFileItem::ModificationTime
), KLocale::FancyLongDate
));
568 d
->m_ownerInfo
->setText(item
.user());
569 d
->m_permissionsInfo
->setText(item
.permissionsString());
571 setItems(KFileItemList() << item
);
574 void KMetaDataWidget::setItems(const KFileItemList
& items
)
576 d
->m_fileItems
= items
;
578 if (items
.count() > 1) {
579 // calculate the size of all items and show this
580 // information to the user
581 d
->m_sizeLabel
->setText(i18nc("@label", "Total Size:"));
582 d
->setRowVisible(d
->m_sizeInfo
, d
->m_sizeVisible
);
584 quint64 totalSize
= 0;
585 foreach (const KFileItem
& item
, items
) {
586 if (!item
.isDir() && !item
.isLink()) {
587 totalSize
+= item
.size();
590 d
->m_sizeInfo
->setText(KIO::convertSize(totalSize
));
594 if (d
->m_nepomukActivated
) {
596 foreach (const KFileItem
& item
, items
) {
597 const KUrl url
= item
.nepomukUri();
603 if (d
->m_loadMetaDataThread
!= 0) {
604 disconnect(d
->m_loadMetaDataThread
, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
605 d
->m_loadMetaDataThread
->cancelAndDelete();
608 d
->m_loadMetaDataThread
= new KLoadMetaDataThread();
609 connect(d
->m_loadMetaDataThread
, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
610 d
->m_loadMetaDataThread
->load(urls
);
615 void KMetaDataWidget::setItem(const KUrl
& url
)
617 KFileItem
item(KFileItem::Unknown
, KFileItem::Unknown
, url
);
622 void KMetaDataWidget::setItems(const QList
<KUrl
>& urls
)
625 foreach (const KUrl
& url
, urls
) {
626 KFileItem
item(KFileItem::Unknown
, KFileItem::Unknown
, url
);
633 KFileItemList
KMetaDataWidget::items() const
635 return d
->m_fileItems
;
638 void KMetaDataWidget::setReadOnly(bool readOnly
)
640 d
->m_readOnly
= readOnly
;
642 // TODO: encapsulate this code as part of a metadata-model for KDE 4.5
643 if (d
->m_taggingWidget
)
644 d
->m_taggingWidget
->setReadOnly(readOnly
);
645 if (d
->m_commentWidget
)
646 d
->m_commentWidget
->setReadOnly(readOnly
);
650 bool KMetaDataWidget::isReadOnly() const
652 return d
->m_readOnly
;
655 void KMetaDataWidget::setVisibleDataTypes(MetaDataTypes data
)
657 d
->m_visibleDataTypes
= data
;
658 d
->updateRowsVisibility();
661 KMetaDataWidget::MetaDataTypes
KMetaDataWidget::visibleDataTypes() const
663 return d
->m_visibleDataTypes
;
666 QSize
KMetaDataWidget::sizeHint() const
668 const int fixedWidth
= 200;
670 int height
= d
->m_gridLayout
->margin() * 2 +
671 d
->m_gridLayout
->spacing() * (d
->m_rows
.count() - 1);
673 foreach (const Private::Row
& row
, d
->m_rows
) {
674 if (row
.infoWidget
!= 0) {
675 int rowHeight
= row
.infoWidget
->heightForWidth(fixedWidth
/ 2);
676 if (rowHeight
<= 0) {
677 rowHeight
= row
.infoWidget
->sizeHint().height();
683 return QSize(fixedWidth
, height
);
686 bool KMetaDataWidget::event(QEvent
* event
)
688 if (event
->type() == QEvent::Polish
) {
689 // The adding of rows is not done in the constructor. This allows the
690 // client of KMetaDataWidget to set a proper foreground role which
691 // will be respected by the rows.
693 d
->addRow(new QLabel(i18nc("@label", "Type:"), this), d
->m_typeInfo
);
694 d
->addRow(d
->m_sizeLabel
, d
->m_sizeInfo
);
695 d
->addRow(new QLabel(i18nc("@label", "Modified:"), this), d
->m_modifiedInfo
);
696 d
->addRow(new QLabel(i18nc("@label", "Owner:"), this), d
->m_ownerInfo
);
697 d
->addRow(new QLabel(i18nc("@label", "Permissions:"), this), d
->m_permissionsInfo
);
700 if (d
->m_nepomukActivated
) {
701 d
->addRow(new QLabel(i18nc("@label", "Rating:"), this), d
->m_ratingWidget
);
702 d
->addRow(new QLabel(i18nc("@label", "Tags:"), this), d
->m_taggingWidget
);
703 d
->addRow(new QLabel(i18nc("@label", "Comment:"), this), d
->m_commentWidget
);
707 d
->updateRowsVisibility();
710 return QWidget::event(event
);
713 #include "kmetadatawidget.moc"