1 /*****************************************************************************
2 * Copyright (C) 2008 by Sebastian Trueg <trueg@kde.org> *
3 * Copyright (C) 2009-2010 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>
28 #include "kmetadatamodel.h"
29 #include "knfotranslator_p.h"
32 #include <QFontMetrics>
33 #include <QGridLayout>
38 #include <config-nepomuk.h>
40 #define DISABLE_NEPOMUK_LEGACY
42 #include "kcommentwidget_p.h"
43 #include "ktaggingwidget_p.h"
45 #include <nepomuk/kratingwidget.h>
46 #include <nepomuk/resource.h>
47 #include <nepomuk/resourcemanager.h>
48 #include <nepomuk/property.h>
49 #include <nepomuk/tag.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
);
110 QList
<KUrl
> sortedKeys(const QHash
<KUrl
, Nepomuk::Variant
>& data
) const;
111 QList
<Nepomuk::Resource
> resourceList() const;
116 bool m_nepomukActivated
;
118 MetaDataTypes m_visibleDataTypes
;
119 QList
<KFileItem
> m_fileItems
;
122 KMetaDataModel
* m_model
;
124 QGridLayout
* m_gridLayout
;
129 QLabel
* m_modifiedInfo
;
131 QLabel
* m_permissionsInfo
;
134 KRatingWidget
* m_ratingWidget
;
135 KTaggingWidget
* m_taggingWidget
;
136 KCommentWidget
* m_commentWidget
;
140 KMetaDataWidget
* const q
;
143 KMetaDataWidget::Private::Private(KMetaDataWidget
* parent
) :
146 m_nepomukActivated(false),
148 m_visibleDataTypes(TypeData
| SizeData
| ModifiedData
| OwnerData
|
149 PermissionsData
| RatingData
| TagsData
| CommentData
),
159 m_permissionsInfo(0),
167 const QFontMetrics
fontMetrics(KGlobalSettings::smallestReadableFont());
169 m_gridLayout
= new QGridLayout(parent
);
170 m_gridLayout
->setMargin(0);
171 m_gridLayout
->setSpacing(fontMetrics
.height() / 4);
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
);
181 m_nepomukActivated
= (Nepomuk::ResourceManager::instance()->init() == 0);
182 if (m_nepomukActivated
) {
183 m_ratingWidget
= new KRatingWidget(parent
);
184 m_ratingWidget
->setFixedHeight(fontMetrics
.height());
185 const Qt::Alignment align
= (parent
->layoutDirection() == Qt::LeftToRight
) ?
186 Qt::AlignLeft
: Qt::AlignRight
;
187 m_ratingWidget
->setAlignment(align
);
188 connect(m_ratingWidget
, SIGNAL(ratingChanged(unsigned int)),
189 q
, SLOT(slotRatingChanged(unsigned int)));
191 m_taggingWidget
= new KTaggingWidget(parent
);
192 connect(m_taggingWidget
, SIGNAL(tagsChanged(const QList
<Nepomuk::Tag
>&)),
193 q
, SLOT(slotTagsChanged(const QList
<Nepomuk::Tag
>&)));
194 connect(m_taggingWidget
, SIGNAL(tagActivated(const Nepomuk::Tag
&)),
195 q
, SLOT(slotTagActivated(const Nepomuk::Tag
&)));
197 m_commentWidget
= new KCommentWidget(parent
);
198 connect(m_commentWidget
, SIGNAL(commentChanged(const QString
&)),
199 q
, SLOT(slotCommentChanged(const QString
&)));
203 initMetaInfoSettings();
206 KMetaDataWidget::Private::~Private()
210 void KMetaDataWidget::Private::addRow(QLabel
* label
, QWidget
* infoWidget
)
214 row
.infoWidget
= infoWidget
;
217 const QFont smallFont
= KGlobalSettings::smallestReadableFont();
218 // use a brighter color for the label and a small font size
219 QPalette palette
= label
->palette();
220 const QPalette::ColorRole role
= q
->foregroundRole();
221 QColor textColor
= palette
.color(role
);
222 textColor
.setAlpha(128);
223 palette
.setColor(role
, textColor
);
224 label
->setPalette(palette
);
225 label
->setForegroundRole(role
);
226 label
->setFont(smallFont
);
227 label
->setWordWrap(true);
228 label
->setAlignment(Qt::AlignTop
| Qt::AlignRight
);
230 infoWidget
->setForegroundRole(role
);
231 QLabel
* infoLabel
= qobject_cast
<QLabel
*>(infoWidget
);
232 if (infoLabel
!= 0) {
233 infoLabel
->setFont(smallFont
);
234 infoLabel
->setWordWrap(true);
235 infoLabel
->setAlignment(Qt::AlignTop
| Qt::AlignLeft
);
238 // add the row to grid layout
239 const int rowIndex
= m_rows
.count();
240 m_gridLayout
->addWidget(label
, rowIndex
, 0, Qt::AlignRight
);
241 const int spacerWidth
= QFontMetrics(smallFont
).size(Qt::TextSingleLine
, " ").width();
242 m_gridLayout
->addItem(new QSpacerItem(spacerWidth
, 1), rowIndex
, 1);
243 m_gridLayout
->addWidget(infoWidget
, rowIndex
, 2, Qt::AlignLeft
);
246 void KMetaDataWidget::Private::setRowVisible(QWidget
* infoWidget
, bool visible
)
248 foreach (const Row
& row
, m_rows
) {
249 if (row
.infoWidget
== infoWidget
) {
250 row
.label
->setVisible(visible
);
251 row
.infoWidget
->setVisible(visible
);
258 void KMetaDataWidget::Private::initMetaInfoSettings()
260 const int currentVersion
= 3; // increase version, if the blacklist of disabled
261 // properties should be updated
263 KConfig
config("kmetainformationrc", KConfig::NoGlobals
);
264 if (config
.group("Misc").readEntry("version", 0) < currentVersion
) {
265 // The resource file is read the first time. Assure
266 // that some meta information is disabled per default.
269 config
.deleteGroup("Show");
270 KConfigGroup settings
= config
.group("Show");
272 static const char* const disabledProperties
[] = {
273 "http://www.semanticdesktop.org/ontologies/2007/01/19/nie#comment",
274 "http://www.semanticdesktop.org/ontologies/2007/01/19/nie#contentSize",
275 "http://www.semanticdesktop.org/ontologies/2007/01/19/nie#depends",
276 "http://www.semanticdesktop.org/ontologies/2007/01/19/nie#isPartOf",
277 "http://www.semanticdesktop.org/ontologies/2007/01/19/nie#lastModified",
278 "http://www.semanticdesktop.org/ontologies/2007/01/19/nie#mimeType",
279 "http://www.semanticdesktop.org/ontologies/2007/01/19/nie#plainTextContent",
280 "http://www.semanticdesktop.org/ontologies/2007/01/19/nie#url",
281 "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#averageBitrate",
282 "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#channels",
283 "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#fileName",
284 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#apertureValue",
285 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#exposureBiasValue",
286 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#exposureTime",
287 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#flash",
288 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#focalLength",
289 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#focalLengthIn35mmFilm",
290 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#isoSpeedRatings",
291 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#make",
292 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#meteringMode",
293 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#model",
294 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#orientation",
295 "http://www.semanticdesktop.org/ontologies/2007/05/10/nexif#whiteBalance",
296 "http://www.semanticdesktop.org/ontologies/2007/08/15/nao#description",
297 "http://www.semanticdesktop.org/ontologies/2007/08/15/nao#hasTag",
298 "http://www.semanticdesktop.org/ontologies/2007/08/15/nao#lastModified",
299 "http://www.semanticdesktop.org/ontologies/2007/08/15/nao#numericRating",
300 "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
302 "kfileitem#permissions",
303 0 // mandatory last entry
306 for (int i
= 0; disabledProperties
[i
] != 0; ++i
) {
307 settings
.writeEntry(disabledProperties
[i
], false);
310 // mark the group as initialized
311 config
.group("Misc").writeEntry("version", currentVersion
);
315 void KMetaDataWidget::Private::updateRowsVisibility()
317 KConfig
config("kmetainformationrc", KConfig::NoGlobals
);
318 KConfigGroup settings
= config
.group("Show");
320 setRowVisible(m_typeInfo
,
321 (m_visibleDataTypes
& KMetaDataWidget::TypeData
) &&
322 settings
.readEntry("kfileitem#type", true));
324 // Cache in m_sizeVisible whether the size should be shown. This
325 // is necessary as the size is temporary hidden when the target
326 // file item is a directory.
327 m_sizeVisible
= (m_visibleDataTypes
& KMetaDataWidget::SizeData
) &&
328 settings
.readEntry("kfileitem#size", true);
329 bool visible
= m_sizeVisible
;
330 if (visible
&& (m_fileItems
.count() == 1)) {
331 // don't show the size information, if one directory is shown
332 const KFileItem item
= m_fileItems
.first();
333 visible
= !item
.isNull() && !item
.isDir();
335 setRowVisible(m_sizeInfo
, visible
);
337 setRowVisible(m_modifiedInfo
,
338 (m_visibleDataTypes
& KMetaDataWidget::ModifiedData
) &&
339 settings
.readEntry("kfileitem#modified", true));
341 setRowVisible(m_ownerInfo
,
342 (m_visibleDataTypes
& KMetaDataWidget::OwnerData
) &&
343 settings
.readEntry("kfileitem#owner", true));
345 setRowVisible(m_permissionsInfo
,
346 (m_visibleDataTypes
& KMetaDataWidget::PermissionsData
) &&
347 settings
.readEntry("kfileitem#permissions", true));
350 if (m_nepomukActivated
) {
351 setRowVisible(m_ratingWidget
,
352 (m_visibleDataTypes
& KMetaDataWidget::RatingData
) &&
353 settings
.readEntry("kfileitem#rating", true));
355 setRowVisible(m_taggingWidget
,
356 (m_visibleDataTypes
& KMetaDataWidget::TagsData
) &&
357 settings
.readEntry("kfileitem#tags", true));
359 setRowVisible(m_commentWidget
,
360 (m_visibleDataTypes
& KMetaDataWidget::CommentData
) &&
361 settings
.readEntry("kfileitem#comment", true));
366 void KMetaDataWidget::Private::slotLoadingFinished()
369 // Show the remaining meta information as text. The number
370 // of required rows may very. Existing rows are reused to
371 // prevent flickering.
372 const KNfoTranslator
& nfo
= KNfoTranslator::instance();
373 int rowIndex
= m_fixedRowCount
;
375 const QHash
<KUrl
, Nepomuk::Variant
> data
= m_model
->data();
376 const QList
<KUrl
> keys
= sortedKeys(data
);
378 foreach (const KUrl
& key
, keys
) {
379 const Nepomuk::Variant value
= data
[key
];
380 const QString itemLabel
= nfo
.translation(key
);
382 bool appliedData
= false;
383 if (m_nepomukActivated
) {
384 const QString keyString
= key
.url();
385 if (keyString
== QLatin1String("kfileitem#rating")) {
386 m_ratingWidget
->setRating(value
.toInt());
388 } else if (keyString
== QLatin1String("kfileitem#comment")) {
389 m_commentWidget
->setText(value
.toString());
391 } else if (keyString
== QLatin1String("kfileitem#tags")) {
392 QList
<Nepomuk::Variant
> variants
= value
.toVariantList();
393 QList
<Nepomuk::Tag
> tags
;
394 foreach (const Nepomuk::Variant
& variant
, variants
) {
395 const Nepomuk::Resource resource
= variant
.toResource();
396 tags
.append(static_cast<Nepomuk::Tag
>(resource
));
398 m_taggingWidget
->setTags(tags
);
405 if (value
.isString()) {
406 itemValue
= value
.toString();
409 if (rowIndex
< m_rows
.count()) {
410 // adjust texts of the current row
411 m_rows
[rowIndex
].label
->setText(itemLabel
);
412 QLabel
* infoValueLabel
= qobject_cast
<QLabel
*>(m_rows
[rowIndex
].infoWidget
);
413 Q_ASSERT(infoValueLabel
!= 0);
414 infoValueLabel
->setText(itemValue
);
417 QLabel
* infoLabel
= new QLabel(itemLabel
, q
);
418 QLabel
* infoValue
= new QLabel(itemValue
, q
);
419 connect(infoValue
, SIGNAL(linkActivated(QString
)),
420 q
, SLOT(slotLinkActivated(QString
)));
421 addRow(infoLabel
, infoValue
);
427 // remove rows that are not needed anymore
428 for (int i
= m_rows
.count() - 1; i
>= rowIndex
; --i
) {
429 delete m_rows
[i
].label
;
430 delete m_rows
[i
].infoWidget
;
438 void KMetaDataWidget::Private::slotRatingChanged(unsigned int rating
)
441 Nepomuk::MassUpdateJob
* job
=
442 Nepomuk::MassUpdateJob::rateResources(resourceList(), rating
);
443 startChangeDataJob(job
);
449 void KMetaDataWidget::Private::slotTagsChanged(const QList
<Nepomuk::Tag
>& tags
)
452 m_taggingWidget
->setTags(tags
);
454 Nepomuk::MassUpdateJob
* job
=
455 Nepomuk::MassUpdateJob::tagResources(resourceList(), tags
);
456 startChangeDataJob(job
);
462 void KMetaDataWidget::Private::slotCommentChanged(const QString
& comment
)
465 Nepomuk::MassUpdateJob
* job
=
466 Nepomuk::MassUpdateJob::commentResources(resourceList(), comment
);
467 startChangeDataJob(job
);
473 void KMetaDataWidget::Private::slotTagActivated(const Nepomuk::Tag
& tag
)
476 emit q
->urlActivated(tag
.resourceUri());
482 void KMetaDataWidget::Private::slotMetaDataUpdateDone()
489 void KMetaDataWidget::Private::slotLinkActivated(const QString
& link
)
491 emit q
->urlActivated(KUrl(link
));
495 void KMetaDataWidget::Private::startChangeDataJob(KJob
* job
)
497 connect(job
, SIGNAL(result(KJob
*)),
498 q
, SLOT(slotMetaDataUpdateDone()));
499 q
->setEnabled(false); // no updates during execution
503 QList
<KUrl
> KMetaDataWidget::Private::sortedKeys(const QHash
<KUrl
, Nepomuk::Variant
>& data
) const
505 const KNfoTranslator
& nfo
= KNfoTranslator::instance();
507 // Create a map, where the translated label prefixed with the
508 // sort priority acts as key. The data of each entry is the URI
509 // of the data. By this the all URIs are sorted by the sort priority
510 // and sub sorted by the translated labels.
511 QMap
<QString
, KUrl
> map
;
512 QHash
<KUrl
, Nepomuk::Variant
>::const_iterator hashIt
= data
.constBegin();
513 while (hashIt
!= data
.constEnd()) {
514 const KUrl uri
= hashIt
.key();
516 QString key
= q
->model()->group(uri
);
517 key
+= nfo
.translation(uri
);
519 map
.insert(key
, uri
);
523 // Apply the URIs from the map to the list that will get returned.
524 // The list will then be alphabetically ordered by the translated labels of the URIs.
526 QMap
<QString
, KUrl
>::const_iterator mapIt
= map
.constBegin();
527 while (mapIt
!= map
.constEnd()) {
528 list
.append(mapIt
.value());
535 QList
<Nepomuk::Resource
> KMetaDataWidget::Private::resourceList() const
537 QList
<Nepomuk::Resource
> list
;
538 foreach (const KFileItem
& item
, m_fileItems
) {
539 const KUrl url
= item
.url();
540 list
.append(Nepomuk::Resource(url
));
546 KMetaDataWidget::KMetaDataWidget(QWidget
* parent
) :
552 KMetaDataWidget::~KMetaDataWidget()
557 void KMetaDataWidget::setItem(const KFileItem
& item
)
559 // update values for "type", "size", "modified",
560 // "owner" and "permissions" synchronously
561 d
->m_sizeLabel
->setText(i18nc("@label", "Size"));
563 d
->m_typeInfo
->setText(i18nc("@label", "Folder"));
564 d
->setRowVisible(d
->m_sizeInfo
, false);
566 d
->m_typeInfo
->setText(item
.mimeComment());
567 d
->m_sizeInfo
->setText(KIO::convertSize(item
.size()));
568 d
->setRowVisible(d
->m_sizeInfo
, d
->m_sizeVisible
);
570 d
->m_modifiedInfo
->setText(KGlobal::locale()->formatDateTime(item
.time(KFileItem::ModificationTime
), KLocale::FancyLongDate
));
571 d
->m_ownerInfo
->setText(item
.user());
572 d
->m_permissionsInfo
->setText(item
.permissionsString());
574 setItems(KFileItemList() << item
);
577 void KMetaDataWidget::setItems(const KFileItemList
& items
)
579 d
->m_fileItems
= items
;
580 if (d
->m_model
!= 0) {
581 d
->m_model
->setItems(items
);
584 if (items
.count() > 1) {
585 // calculate the size of all items and show this
586 // information to the user
587 d
->m_sizeLabel
->setText(i18nc("@label", "Total Size:"));
588 d
->setRowVisible(d
->m_sizeInfo
, d
->m_sizeVisible
);
590 quint64 totalSize
= 0;
591 foreach (const KFileItem
& item
, items
) {
592 if (!item
.isDir() && !item
.isLink()) {
593 totalSize
+= item
.size();
596 d
->m_sizeInfo
->setText(KIO::convertSize(totalSize
));
600 void KMetaDataWidget::setItem(const KUrl
& url
)
602 KFileItem
item(KFileItem::Unknown
, KFileItem::Unknown
, url
);
607 void KMetaDataWidget::setItems(const QList
<KUrl
>& urls
)
610 foreach (const KUrl
& url
, urls
) {
611 KFileItem
item(KFileItem::Unknown
, KFileItem::Unknown
, url
);
618 KFileItemList
KMetaDataWidget::items() const
620 return d
->m_fileItems
;
623 void KMetaDataWidget::setModel(KMetaDataModel
* model
)
625 if (d
->m_model
!= 0) {
626 disconnect(d
->m_model
, SIGNAL(loadingFinished()));
629 connect(d
->m_model
, SIGNAL(loadingFinished()), this, SLOT(slotLoadingFinished()));
632 KMetaDataModel
* KMetaDataWidget::model() const
637 void KMetaDataWidget::setReadOnly(bool readOnly
)
639 d
->m_readOnly
= readOnly
;
641 // TODO: encapsulate this code as part of a metadata-model for KDE 4.5
642 if (d
->m_taggingWidget
)
643 d
->m_taggingWidget
->setReadOnly(readOnly
);
644 if (d
->m_commentWidget
)
645 d
->m_commentWidget
->setReadOnly(readOnly
);
649 bool KMetaDataWidget::isReadOnly() const
651 return d
->m_readOnly
;
654 void KMetaDataWidget::setVisibleDataTypes(MetaDataTypes data
)
656 d
->m_visibleDataTypes
= data
;
657 d
->updateRowsVisibility();
660 KMetaDataWidget::MetaDataTypes
KMetaDataWidget::visibleDataTypes() const
662 return d
->m_visibleDataTypes
;
665 QSize
KMetaDataWidget::sizeHint() const
667 const int fixedWidth
= 200;
669 int height
= d
->m_gridLayout
->margin() * 2 +
670 d
->m_gridLayout
->spacing() * (d
->m_rows
.count() - 1);
672 foreach (const Private::Row
& row
, d
->m_rows
) {
673 if (row
.infoWidget
!= 0) {
674 int rowHeight
= row
.infoWidget
->heightForWidth(fixedWidth
/ 2);
675 if (rowHeight
<= 0) {
676 rowHeight
= row
.infoWidget
->sizeHint().height();
682 return QSize(fixedWidth
, height
);
685 bool KMetaDataWidget::event(QEvent
* event
)
687 if (event
->type() == QEvent::Polish
) {
688 // The adding of rows is not done in the constructor. This allows the
689 // client of KMetaDataWidget to set a proper foreground role which
690 // will be respected by the rows.
692 d
->addRow(new QLabel(i18nc("@label file type", "Type"), this), d
->m_typeInfo
);
693 d
->addRow(d
->m_sizeLabel
, d
->m_sizeInfo
);
694 d
->addRow(new QLabel(i18nc("@label", "Modified"), this), d
->m_modifiedInfo
);
695 d
->addRow(new QLabel(i18nc("@label", "Owner"), this), d
->m_ownerInfo
);
696 d
->addRow(new QLabel(i18nc("@label", "Permissions"), this), d
->m_permissionsInfo
);
699 if (d
->m_nepomukActivated
) {
700 d
->addRow(new QLabel(i18nc("@label", "Rating"), this), d
->m_ratingWidget
);
701 d
->addRow(new QLabel(i18nc("@label", "Tags"), this), d
->m_taggingWidget
);
702 d
->addRow(new QLabel(i18nc("@label", "Comment"), this), d
->m_commentWidget
);
706 // The current number of rows represents meta data, that will be shown for
707 // all files. Dynamic meta data will be appended after those rows (see
708 // slotLoadingFinished()).
709 d
->m_fixedRowCount
= d
->m_rows
.count();
711 d
->updateRowsVisibility();
714 return QWidget::event(event
);
717 #include "kmetadatawidget.moc"