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 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()
209 if (m_loadMetaDataThread
!= 0) {
210 disconnect(m_loadMetaDataThread
, SIGNAL(finished()), q
, SLOT(slotLoadingFinished()));
211 m_loadMetaDataThread
->cancelAndDelete();
212 m_loadMetaDataThread
= 0;
217 void KMetaDataWidget::Private::addRow(QLabel
* label
, QWidget
* infoWidget
)
221 row
.infoWidget
= infoWidget
;
224 const QFont smallFont
= KGlobalSettings::smallestReadableFont();
225 // use a brighter color for the label and a small font size
226 QPalette palette
= label
->palette();
227 const QPalette::ColorRole role
= q
->foregroundRole();
228 QColor textColor
= palette
.color(role
);
229 textColor
.setAlpha(128);
230 palette
.setColor(role
, textColor
);
231 label
->setPalette(palette
);
232 label
->setForegroundRole(role
);
233 label
->setFont(smallFont
);
234 label
->setWordWrap(true);
235 label
->setAlignment(Qt::AlignTop
| Qt::AlignRight
);
237 infoWidget
->setForegroundRole(role
);
238 QLabel
* infoLabel
= qobject_cast
<QLabel
*>(infoWidget
);
239 if (infoLabel
!= 0) {
240 infoLabel
->setFont(smallFont
);
241 infoLabel
->setWordWrap(true);
242 infoLabel
->setAlignment(Qt::AlignTop
| Qt::AlignLeft
);
245 // add the row to grid layout
246 const int rowIndex
= m_rows
.count();
247 m_gridLayout
->addWidget(label
, rowIndex
, 0, Qt::AlignRight
);
248 const int spacerWidth
= QFontMetrics(smallFont
).size(Qt::TextSingleLine
, " ").width();
249 m_gridLayout
->addItem(new QSpacerItem(spacerWidth
, 1), rowIndex
, 1);
250 m_gridLayout
->addWidget(infoWidget
, rowIndex
, 2, Qt::AlignLeft
);
253 void KMetaDataWidget::Private::setRowVisible(QWidget
* infoWidget
, bool visible
)
255 foreach (const Row
& row
, m_rows
) {
256 if (row
.infoWidget
== infoWidget
) {
257 row
.label
->setVisible(visible
);
258 row
.infoWidget
->setVisible(visible
);
265 void KMetaDataWidget::Private::initMetaInfoSettings()
267 const int currentVersion
= 1; // increase version, if the blacklist of disabled
268 // properties should be updated
270 KConfig
config("kmetainformationrc", KConfig::NoGlobals
);
271 if (config
.group("Misc").readEntry("version", 0) < currentVersion
) {
272 // The resource file is read the first time. Assure
273 // that some meta information is disabled per default.
276 config
.deleteGroup( "Show" );
277 KConfigGroup settings
= config
.group("Show");
279 // trueg: KDE 4.5: use a blacklist of actual rdf properties
281 static const char* disabledProperties
[] = {
282 "asText", "contentSize", "created", "depth", "description", "fileExtension",
283 "fileName", "fileSize", "hasTag", "isPartOf", "lastModified", "mimeType", "name",
284 "numericRating", "parentUrl", "permissions", "plainTextContent", "owner",
285 "sourceModified", "url",
286 0 // mandatory last entry
289 for (int i
= 0; disabledProperties
[i
] != 0; ++i
) {
290 settings
.writeEntry(disabledProperties
[i
], false);
293 // mark the group as initialized
294 config
.group("Misc").writeEntry("version", currentVersion
);
298 void KMetaDataWidget::Private::updateRowsVisibility()
300 KConfig
config("kmetainformationrc", KConfig::NoGlobals
);
301 KConfigGroup settings
= config
.group("Show");
303 setRowVisible(m_typeInfo
,
304 (m_visibleDataTypes
& KMetaDataWidget::TypeData
) &&
305 settings
.readEntry("type", true));
307 // Cache in m_sizeVisible whether the size should be shown. This
308 // is necessary as the size is temporary hidden when the target
309 // file item is a directory.
310 m_sizeVisible
= (m_visibleDataTypes
& KMetaDataWidget::SizeData
) &&
311 settings
.readEntry("size", true);
312 setRowVisible(m_sizeInfo
, m_sizeVisible
);
314 setRowVisible(m_modifiedInfo
,
315 (m_visibleDataTypes
& KMetaDataWidget::ModifiedData
) &&
316 settings
.readEntry("modified", true));
318 setRowVisible(m_ownerInfo
,
319 (m_visibleDataTypes
& KMetaDataWidget::OwnerData
) &&
320 settings
.readEntry("owner", true));
322 setRowVisible(m_permissionsInfo
,
323 (m_visibleDataTypes
& KMetaDataWidget::PermissionsData
) &&
324 settings
.readEntry("permissions", true));
327 if (m_nepomukActivated
) {
328 setRowVisible(m_ratingWidget
,
329 (m_visibleDataTypes
& KMetaDataWidget::RatingData
) &&
330 settings
.readEntry("rating", true));
332 setRowVisible(m_taggingWidget
,
333 (m_visibleDataTypes
& KMetaDataWidget::TagsData
) &&
334 settings
.readEntry("tags", true));
336 setRowVisible(m_commentWidget
,
337 (m_visibleDataTypes
& KMetaDataWidget::CommentData
) &&
338 settings
.readEntry("comment", true));
343 void KMetaDataWidget::Private::slotLoadingFinished()
346 if (m_loadMetaDataThread
== 0) {
347 // The signal finished() has been emitted, but the thread has been marked
348 // as invalid in the meantime. Just ignore the signal in this case.
352 Q_ASSERT(m_ratingWidget
!= 0);
353 Q_ASSERT(m_commentWidget
!= 0);
354 Q_ASSERT(m_taggingWidget
!= 0);
355 m_ratingWidget
->setRating(m_loadMetaDataThread
->rating());
356 m_commentWidget
->setText(m_loadMetaDataThread
->comment());
357 m_taggingWidget
->setTags(m_loadMetaDataThread
->tags());
359 // Show the remaining meta information as text. The number
360 // of required rows may very. Existing rows are reused to
361 // prevent flickering.
362 int index
= 8; // TODO: don't hardcode this value here
363 const int rowCount
= m_rows
.count();
364 Q_ASSERT(rowCount
>= index
);
366 const QList
<KLoadMetaDataThread::Item
> items
= mergedItems(m_loadMetaDataThread
->items());
367 foreach (const KLoadMetaDataThread::Item
& item
, items
) {
368 if (index
< rowCount
) {
369 // adjust texts of the current row
370 m_rows
[index
].label
->setText(item
.label
);
371 QLabel
* infoValueLabel
= qobject_cast
<QLabel
*>(m_rows
[index
].infoWidget
);
372 Q_ASSERT(infoValueLabel
!= 0);
373 infoValueLabel
->setText(item
.value
);
376 QLabel
* infoLabel
= new QLabel(item
.label
, q
);
377 QLabel
* infoValue
= new QLabel(item
.value
, q
);
378 connect(infoValue
, SIGNAL(linkActivated(QString
)),
379 q
, SLOT(slotLinkActivated(QString
)));
380 addRow(infoLabel
, infoValue
);
384 if (items
.count() > 0) {
388 // remove rows that are not needed anymore
389 for (int i
= m_rows
.count() - 1; i
>= index
; --i
) {
390 delete m_rows
[i
].label
;
391 delete m_rows
[i
].infoWidget
;
395 m_files
= m_loadMetaDataThread
->files();
397 delete m_loadMetaDataThread
;
398 m_loadMetaDataThread
= 0;
404 void KMetaDataWidget::Private::slotRatingChanged(unsigned int rating
)
407 Nepomuk::MassUpdateJob
* job
=
408 Nepomuk::MassUpdateJob::rateResources(m_files
.values(), rating
);
409 startChangeDataJob(job
);
415 void KMetaDataWidget::Private::slotTagsChanged(const QList
<Nepomuk::Tag
>& tags
)
418 m_taggingWidget
->setTags(tags
);
420 Nepomuk::MassUpdateJob
* job
=
421 Nepomuk::MassUpdateJob::tagResources(m_files
.values(), tags
);
422 startChangeDataJob(job
);
428 void KMetaDataWidget::Private::slotCommentChanged(const QString
& comment
)
431 Nepomuk::MassUpdateJob
* job
=
432 Nepomuk::MassUpdateJob::commentResources(m_files
.values(), comment
);
433 startChangeDataJob(job
);
439 void KMetaDataWidget::Private::slotTagActivated(const Nepomuk::Tag
& tag
)
442 emit q
->urlActivated(tag
.resourceUri());
448 void KMetaDataWidget::Private::slotMetaDataUpdateDone()
455 void KMetaDataWidget::Private::slotLinkActivated(const QString
& link
)
457 emit q
->urlActivated(KUrl(link
));
461 void KMetaDataWidget::Private::startChangeDataJob(KJob
* job
)
463 connect(job
, SIGNAL(result(KJob
*)),
464 q
, SLOT(slotMetaDataUpdateDone()));
465 q
->setEnabled(false); // no updates during execution
469 QList
<KLoadMetaDataThread::Item
>
470 KMetaDataWidget::Private::mergedItems(const QList
<KLoadMetaDataThread::Item
>& items
)
472 // TODO: Currently only "width" and "height" are merged as "width x height". If
473 // other kind of merges should be done too, a more general approach is required.
474 QList
<KLoadMetaDataThread::Item
> mergedItems
;
476 KLoadMetaDataThread::Item width
;
477 KLoadMetaDataThread::Item height
;
479 foreach (const KLoadMetaDataThread::Item
& item
, items
) {
480 if (item
.name
== "width") {
482 } else if (item
.name
== "height") {
485 // insert the item sorted by the label
486 bool inserted
= false;
488 const int count
= mergedItems
.count();
489 while (!inserted
&& (i
< count
)) {
490 if (item
.label
< mergedItems
[i
].label
) {
491 mergedItems
.insert(i
, item
);
497 mergedItems
.append(item
);
502 const bool foundWidth
= !width
.name
.isEmpty();
503 const bool foundHeight
= !height
.name
.isEmpty();
504 if (foundWidth
&& !foundHeight
) {
505 mergedItems
.insert(0, width
);
506 } else if (foundHeight
&& !foundWidth
) {
507 mergedItems
.insert(0, height
);
508 } else if (foundWidth
&& foundHeight
) {
509 KLoadMetaDataThread::Item size
;
510 size
.label
= i18nc("@label", "Width x Height:");
511 size
.value
= width
.value
+ " x " + height
.value
;
512 mergedItems
.insert(0, size
);
519 KMetaDataWidget::KMetaDataWidget(QWidget
* parent
) :
525 KMetaDataWidget::~KMetaDataWidget()
530 void KMetaDataWidget::setItem(const KFileItem
& item
)
532 // update values for "type", "size", "modified",
533 // "owner" and "permissions" synchronously
534 d
->m_sizeLabel
->setText(i18nc("@label", "Size:"));
536 d
->m_typeInfo
->setText(i18nc("@label", "Folder"));
537 d
->setRowVisible(d
->m_sizeInfo
, false);
539 d
->m_typeInfo
->setText(item
.mimeComment());
540 d
->m_sizeInfo
->setText(KIO::convertSize(item
.size()));
541 d
->setRowVisible(d
->m_sizeInfo
, d
->m_sizeVisible
);
543 d
->m_modifiedInfo
->setText(KGlobal::locale()->formatDateTime(item
.time(KFileItem::ModificationTime
), KLocale::FancyLongDate
));
544 d
->m_ownerInfo
->setText(item
.user());
545 d
->m_permissionsInfo
->setText(item
.permissionsString());
547 setItems(KFileItemList() << item
);
550 void KMetaDataWidget::setItems(const KFileItemList
& items
)
552 d
->m_fileItems
= items
;
554 if (items
.count() > 1) {
555 // calculate the size of all items and show this
556 // information to the user
557 d
->m_sizeLabel
->setText(i18nc("@label", "Total Size:"));
558 d
->setRowVisible(d
->m_sizeInfo
, d
->m_sizeVisible
);
560 quint64 totalSize
= 0;
561 foreach (const KFileItem
& item
, items
) {
562 if (!item
.isDir() && !item
.isLink()) {
563 totalSize
+= item
.size();
566 d
->m_sizeInfo
->setText(KIO::convertSize(totalSize
));
570 if (d
->m_nepomukActivated
) {
572 foreach (const KFileItem
& item
, items
) {
573 const KUrl url
= item
.nepomukUri();
579 if (d
->m_loadMetaDataThread
!= 0) {
580 disconnect(d
->m_loadMetaDataThread
, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
581 d
->m_loadMetaDataThread
->cancelAndDelete();
584 d
->m_loadMetaDataThread
= new KLoadMetaDataThread();
585 connect(d
->m_loadMetaDataThread
, SIGNAL(finished()), this, SLOT(slotLoadingFinished()));
586 d
->m_loadMetaDataThread
->load(urls
);
591 void KMetaDataWidget::setItem(const KUrl
& url
)
593 KFileItem
item(KFileItem::Unknown
, KFileItem::Unknown
, url
);
598 void KMetaDataWidget::setItems(const QList
<KUrl
>& urls
)
601 foreach (const KUrl
& url
, urls
) {
602 KFileItem
item(KFileItem::Unknown
, KFileItem::Unknown
, url
);
609 KFileItemList
KMetaDataWidget::items() const
611 return d
->m_fileItems
;
614 void KMetaDataWidget::setReadOnly(bool readOnly
)
616 d
->m_readOnly
= readOnly
;
618 // TODO: encapsulate this code as part of a metadata-model for KDE 4.5
619 if (d
->m_taggingWidget
)
620 d
->m_taggingWidget
->setReadOnly(readOnly
);
621 if (d
->m_commentWidget
)
622 d
->m_commentWidget
->setReadOnly(readOnly
);
626 bool KMetaDataWidget::isReadOnly() const
628 return d
->m_readOnly
;
631 void KMetaDataWidget::setVisibleDataTypes(MetaDataTypes data
)
633 d
->m_visibleDataTypes
= data
;
634 d
->updateRowsVisibility();
637 KMetaDataWidget::MetaDataTypes
KMetaDataWidget::visibleDataTypes() const
639 return d
->m_visibleDataTypes
;
642 QSize
KMetaDataWidget::sizeHint() const
644 const int fixedWidth
= 200;
646 int height
= d
->m_gridLayout
->margin() * 2 +
647 d
->m_gridLayout
->spacing() * (d
->m_rows
.count() - 1);
649 foreach (const Private::Row
& row
, d
->m_rows
) {
650 if (row
.infoWidget
!= 0) {
651 int rowHeight
= row
.infoWidget
->heightForWidth(fixedWidth
/ 2);
652 if (rowHeight
<= 0) {
653 rowHeight
= row
.infoWidget
->sizeHint().height();
659 return QSize(fixedWidth
, height
);
662 bool KMetaDataWidget::event(QEvent
* event
)
664 if (event
->type() == QEvent::Polish
) {
665 // The adding of rows is not done in the constructor. This allows the
666 // client of KMetaDataWidget to set a proper foreground role which
667 // will be respected by the rows.
669 d
->addRow(new QLabel(i18nc("@label", "Type:"), this), d
->m_typeInfo
);
670 d
->addRow(d
->m_sizeLabel
, d
->m_sizeInfo
);
671 d
->addRow(new QLabel(i18nc("@label", "Modified:"), this), d
->m_modifiedInfo
);
672 d
->addRow(new QLabel(i18nc("@label", "Owner:"), this), d
->m_ownerInfo
);
673 d
->addRow(new QLabel(i18nc("@label", "Permissions:"), this), d
->m_permissionsInfo
);
676 if (d
->m_nepomukActivated
) {
677 d
->addRow(new QLabel(i18nc("@label", "Rating:"), this), d
->m_ratingWidget
);
678 d
->addRow(new QLabel(i18nc("@label", "Tags:"), this), d
->m_taggingWidget
);
679 d
->addRow(new QLabel(i18nc("@label", "Comment:"), this), d
->m_commentWidget
);
683 d
->updateRowsVisibility();
686 return QWidget::event(event
);
689 #include "kmetadatawidget.moc"