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 setRowVisible(m_sizeInfo
, m_sizeVisible
);
317 setRowVisible(m_modifiedInfo
,
318 (m_visibleDataTypes
& KMetaDataWidget::ModifiedData
) &&
319 settings
.readEntry("modified", true));
321 setRowVisible(m_ownerInfo
,
322 (m_visibleDataTypes
& KMetaDataWidget::OwnerData
) &&
323 settings
.readEntry("owner", true));
325 setRowVisible(m_permissionsInfo
,
326 (m_visibleDataTypes
& KMetaDataWidget::PermissionsData
) &&
327 settings
.readEntry("permissions", true));
330 if (m_nepomukActivated
) {
331 setRowVisible(m_ratingWidget
,
332 (m_visibleDataTypes
& KMetaDataWidget::RatingData
) &&
333 settings
.readEntry("rating", true));
335 setRowVisible(m_taggingWidget
,
336 (m_visibleDataTypes
& KMetaDataWidget::TagsData
) &&
337 settings
.readEntry("tags", true));
339 setRowVisible(m_commentWidget
,
340 (m_visibleDataTypes
& KMetaDataWidget::CommentData
) &&
341 settings
.readEntry("comment", true));
346 void KMetaDataWidget::Private::slotLoadingFinished()
349 if (m_loadMetaDataThread
== 0) {
350 // The signal finished() has been emitted, but the thread has been marked
351 // as invalid in the meantime. Just ignore the signal in this case.
355 Q_ASSERT(m_ratingWidget
!= 0);
356 Q_ASSERT(m_commentWidget
!= 0);
357 Q_ASSERT(m_taggingWidget
!= 0);
358 m_ratingWidget
->setRating(m_loadMetaDataThread
->rating());
359 m_commentWidget
->setText(m_loadMetaDataThread
->comment());
360 m_taggingWidget
->setTags(m_loadMetaDataThread
->tags());
362 // Show the remaining meta information as text. The number
363 // of required rows may very. Existing rows are reused to
364 // prevent flickering.
365 int index
= 8; // TODO: don't hardcode this value here
366 const int rowCount
= m_rows
.count();
367 Q_ASSERT(rowCount
>= index
);
369 const QList
<KLoadMetaDataThread::Item
> items
= mergedItems(m_loadMetaDataThread
->items());
370 foreach (const KLoadMetaDataThread::Item
& item
, items
) {
371 const QString itemLabel
= item
.label
;
372 QString itemValue
= item
.value
;
373 if (item
.value
.startsWith("<a href=")) {
374 // use the text color for the value-links, to create a visual difference
375 // to the semantically different links like "Change..."
376 const QColor linkColor
= q
->palette().text().color();
379 decoration
= QString::fromLatin1("text-decoration:none;");
381 const QString styleText
= QString::fromLatin1("style=\"color:%1;%2\" ")
382 .arg(linkColor
.name())
384 itemValue
.insert(3 /* after "<a "*/, styleText
);
386 if (index
< rowCount
) {
387 // adjust texts of the current row
388 m_rows
[index
].label
->setText(itemLabel
);
389 QLabel
* infoValueLabel
= qobject_cast
<QLabel
*>(m_rows
[index
].infoWidget
);
390 Q_ASSERT(infoValueLabel
!= 0);
391 infoValueLabel
->setText(itemValue
);
394 QLabel
* infoLabel
= new QLabel(itemLabel
, q
);
395 QLabel
* infoValue
= new QLabel(itemValue
, q
);
396 connect(infoValue
, SIGNAL(linkActivated(QString
)),
397 q
, SLOT(slotLinkActivated(QString
)));
398 addRow(infoLabel
, infoValue
);
402 if (items
.count() > 0) {
406 // remove rows that are not needed anymore
407 for (int i
= m_rows
.count() - 1; i
>= index
; --i
) {
408 delete m_rows
[i
].label
;
409 delete m_rows
[i
].infoWidget
;
413 m_files
= m_loadMetaDataThread
->files();
415 delete m_loadMetaDataThread
;
416 m_loadMetaDataThread
= 0;
422 void KMetaDataWidget::Private::slotRatingChanged(unsigned int rating
)
425 Nepomuk::MassUpdateJob
* job
=
426 Nepomuk::MassUpdateJob::rateResources(m_files
.values(), rating
);
427 startChangeDataJob(job
);
433 void KMetaDataWidget::Private::slotTagsChanged(const QList
<Nepomuk::Tag
>& tags
)
436 m_taggingWidget
->setTags(tags
);
438 Nepomuk::MassUpdateJob
* job
=
439 Nepomuk::MassUpdateJob::tagResources(m_files
.values(), tags
);
440 startChangeDataJob(job
);
446 void KMetaDataWidget::Private::slotCommentChanged(const QString
& comment
)
449 Nepomuk::MassUpdateJob
* job
=
450 Nepomuk::MassUpdateJob::commentResources(m_files
.values(), comment
);
451 startChangeDataJob(job
);
457 void KMetaDataWidget::Private::slotTagActivated(const Nepomuk::Tag
& tag
)
460 emit q
->urlActivated(tag
.resourceUri());
466 void KMetaDataWidget::Private::slotMetaDataUpdateDone()
473 void KMetaDataWidget::Private::slotLinkActivated(const QString
& link
)
475 emit q
->urlActivated(KUrl(link
));
479 void KMetaDataWidget::Private::startChangeDataJob(KJob
* job
)
481 connect(job
, SIGNAL(result(KJob
*)),
482 q
, SLOT(slotMetaDataUpdateDone()));
483 q
->setEnabled(false); // no updates during execution
487 QList
<KLoadMetaDataThread::Item
>
488 KMetaDataWidget::Private::mergedItems(const QList
<KLoadMetaDataThread::Item
>& items
)
490 // TODO: Currently only "width" and "height" are merged as "width x height". If
491 // other kind of merges should be done too, a more general approach is required.
492 QList
<KLoadMetaDataThread::Item
> mergedItems
;
494 KLoadMetaDataThread::Item width
;
495 KLoadMetaDataThread::Item height
;
497 foreach (const KLoadMetaDataThread::Item
& item
, items
) {
498 if (item
.name
== "width") {
500 } else if (item
.name
== "height") {
503 // insert the item sorted by the label
504 bool inserted
= false;
506 const int count
= mergedItems
.count();
507 while (!inserted
&& (i
< count
)) {
508 if (item
.label
< mergedItems
[i
].label
) {
509 mergedItems
.insert(i
, item
);
515 mergedItems
.append(item
);
520 const bool foundWidth
= !width
.name
.isEmpty();
521 const bool foundHeight
= !height
.name
.isEmpty();
522 if (foundWidth
&& !foundHeight
) {
523 mergedItems
.insert(0, width
);
524 } else if (foundHeight
&& !foundWidth
) {
525 mergedItems
.insert(0, height
);
526 } else if (foundWidth
&& foundHeight
) {
527 KLoadMetaDataThread::Item size
;
528 size
.label
= i18nc("@label", "Width x Height:");
529 size
.value
= width
.value
+ " x " + height
.value
;
530 mergedItems
.insert(0, size
);
537 KMetaDataWidget::KMetaDataWidget(QWidget
* parent
) :
543 KMetaDataWidget::~KMetaDataWidget()
548 void KMetaDataWidget::setItem(const KFileItem
& item
)
550 // update values for "type", "size", "modified",
551 // "owner" and "permissions" synchronously
552 d
->m_sizeLabel
->setText(i18nc("@label", "Size:"));
554 d
->m_typeInfo
->setText(i18nc("@label", "Folder"));
555 d
->setRowVisible(d
->m_sizeInfo
, false);
557 d
->m_typeInfo
->setText(item
.mimeComment());
558 d
->m_sizeInfo
->setText(KIO::convertSize(item
.size()));
559 d
->setRowVisible(d
->m_sizeInfo
, d
->m_sizeVisible
);
561 d
->m_modifiedInfo
->setText(KGlobal::locale()->formatDateTime(item
.time(KFileItem::ModificationTime
), KLocale::FancyLongDate
));
562 d
->m_ownerInfo
->setText(item
.user());
563 d
->m_permissionsInfo
->setText(item
.permissionsString());
565 setItems(KFileItemList() << item
);
568 void KMetaDataWidget::setItems(const KFileItemList
& items
)
570 d
->m_fileItems
= items
;
572 if (items
.count() > 1) {
573 // calculate the size of all items and show this
574 // information to the user
575 d
->m_sizeLabel
->setText(i18nc("@label", "Total Size:"));
576 d
->setRowVisible(d
->m_sizeInfo
, d
->m_sizeVisible
);
578 quint64 totalSize
= 0;
579 foreach (const KFileItem
& item
, items
) {
580 if (!item
.isDir() && !item
.isLink()) {
581 totalSize
+= item
.size();
584 d
->m_sizeInfo
->setText(KIO::convertSize(totalSize
));
588 if (d
->m_nepomukActivated
) {
590 foreach (const KFileItem
& item
, items
) {
591 const KUrl url
= item
.nepomukUri();
597 if (d
->m_loadMetaDataThread
!= 0) {
598 disconnect(d
->m_loadMetaDataThread
, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
599 d
->m_loadMetaDataThread
->cancelAndDelete();
602 d
->m_loadMetaDataThread
= new KLoadMetaDataThread();
603 connect(d
->m_loadMetaDataThread
, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
604 d
->m_loadMetaDataThread
->load(urls
);
609 void KMetaDataWidget::setItem(const KUrl
& url
)
611 KFileItem
item(KFileItem::Unknown
, KFileItem::Unknown
, url
);
616 void KMetaDataWidget::setItems(const QList
<KUrl
>& urls
)
619 foreach (const KUrl
& url
, urls
) {
620 KFileItem
item(KFileItem::Unknown
, KFileItem::Unknown
, url
);
627 KFileItemList
KMetaDataWidget::items() const
629 return d
->m_fileItems
;
632 void KMetaDataWidget::setReadOnly(bool readOnly
)
634 d
->m_readOnly
= readOnly
;
636 // TODO: encapsulate this code as part of a metadata-model for KDE 4.5
637 if (d
->m_taggingWidget
)
638 d
->m_taggingWidget
->setReadOnly(readOnly
);
639 if (d
->m_commentWidget
)
640 d
->m_commentWidget
->setReadOnly(readOnly
);
644 bool KMetaDataWidget::isReadOnly() const
646 return d
->m_readOnly
;
649 void KMetaDataWidget::setVisibleDataTypes(MetaDataTypes data
)
651 d
->m_visibleDataTypes
= data
;
652 d
->updateRowsVisibility();
655 KMetaDataWidget::MetaDataTypes
KMetaDataWidget::visibleDataTypes() const
657 return d
->m_visibleDataTypes
;
660 QSize
KMetaDataWidget::sizeHint() const
662 const int fixedWidth
= 200;
664 int height
= d
->m_gridLayout
->margin() * 2 +
665 d
->m_gridLayout
->spacing() * (d
->m_rows
.count() - 1);
667 foreach (const Private::Row
& row
, d
->m_rows
) {
668 if (row
.infoWidget
!= 0) {
669 int rowHeight
= row
.infoWidget
->heightForWidth(fixedWidth
/ 2);
670 if (rowHeight
<= 0) {
671 rowHeight
= row
.infoWidget
->sizeHint().height();
677 return QSize(fixedWidth
, height
);
680 bool KMetaDataWidget::event(QEvent
* event
)
682 if (event
->type() == QEvent::Polish
) {
683 // The adding of rows is not done in the constructor. This allows the
684 // client of KMetaDataWidget to set a proper foreground role which
685 // will be respected by the rows.
687 d
->addRow(new QLabel(i18nc("@label", "Type:"), this), d
->m_typeInfo
);
688 d
->addRow(d
->m_sizeLabel
, d
->m_sizeInfo
);
689 d
->addRow(new QLabel(i18nc("@label", "Modified:"), this), d
->m_modifiedInfo
);
690 d
->addRow(new QLabel(i18nc("@label", "Owner:"), this), d
->m_ownerInfo
);
691 d
->addRow(new QLabel(i18nc("@label", "Permissions:"), this), d
->m_permissionsInfo
);
694 if (d
->m_nepomukActivated
) {
695 d
->addRow(new QLabel(i18nc("@label", "Rating:"), this), d
->m_ratingWidget
);
696 d
->addRow(new QLabel(i18nc("@label", "Tags:"), this), d
->m_taggingWidget
);
697 d
->addRow(new QLabel(i18nc("@label", "Comment:"), this), d
->m_commentWidget
);
701 d
->updateRowsVisibility();
704 return QWidget::event(event
);
707 #include "kmetadatawidget.moc"