diff --git a/src/filemetadatawidget.cpp b/src/filemetadatawidget.cpp index 949d6b8..0783fa3 100644 --- a/src/filemetadatawidget.cpp +++ b/src/filemetadatawidget.cpp @@ -1,391 +1,389 @@ /* Copyright (C) 2012-2013 Vishesh Handa Adapted from KFileMetadataWidget Copyright (C) 2008 by Sebastian Trueg Copyright (C) 2009-2010 by Peter Penz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "filemetadatawidget.h" #include "metadatafilter.h" #include "widgetfactory.h" #include "filemetadataprovider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Baloo; class Baloo::FileMetaDataWidget::Private { public: struct Row { QCheckBox* checkBox; QLabel* label; QWidget* value; }; Private(FileMetaDataWidget* parent); ~Private(); void deleteRows(); void slotLoadingFinished(); void slotLinkActivated(const QString& link); void slotDataChangeStarted(); void slotDataChangeFinished(); QStringList sortedKeys(const QVariantMap& data) const; QLabel* createLabel(const QString &key, const QString& itemLabel, FileMetaDataWidget* parent); void saveConfig(); QList m_rows; FileMetaDataProvider* m_provider; QGridLayout* m_gridLayout; MetadataFilter* m_filter; WidgetFactory* m_widgetFactory; QMap m_visibilityChanged; bool m_configureVisibleProperties = false; private: FileMetaDataWidget* const q; }; FileMetaDataWidget::Private::Private(FileMetaDataWidget* parent) : m_rows() , m_provider(nullptr) , m_gridLayout(nullptr) , q(parent) { m_filter = new MetadataFilter(q); m_widgetFactory = new WidgetFactory(q); connect(m_widgetFactory, &WidgetFactory::urlActivated, q, &FileMetaDataWidget::urlActivated); // TODO: If KFileMetaDataProvider might get a public class in future KDE releases, // the following code should be moved into KFileMetaDataWidget::setModel(): m_provider = new FileMetaDataProvider(q); connect(m_provider, &FileMetaDataProvider::loadingFinished, q, [this](){ slotLoadingFinished(); }); } FileMetaDataWidget::Private::~Private() { } void FileMetaDataWidget::Private::deleteRows() { for (const Row& row : qAsConst(m_rows)) { delete row.label; row.value->deleteLater(); if (row.checkBox) { row.checkBox->deleteLater(); } } m_rows.clear(); } QLabel* FileMetaDataWidget::Private::createLabel(const QString &key, const QString& itemLabel, FileMetaDataWidget* parent) { QLabel* label = new QLabel(itemLabel + QLatin1Char(':'), parent); label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); label->setForegroundRole(parent->foregroundRole()); label->setFont(parent->font()); label->setWordWrap(true); label->setAlignment(Qt::AlignTop | Qt::AlignRight); label->setObjectName(QStringLiteral("L_%1").arg(key)); return label; } void FileMetaDataWidget::Private::slotLoadingFinished() { deleteRows(); if (m_gridLayout == nullptr) { m_gridLayout = new QGridLayout(q); m_gridLayout->setContentsMargins(0, 0, 0, 0); m_gridLayout->setSpacing(q->fontMetrics().height() / 4); } QVariantMap data = m_provider->data(); QStringList active; if (m_configureVisibleProperties) { active = m_filter->filter(data).keys(); auto changedIt = m_visibilityChanged.constBegin(); while (changedIt != m_visibilityChanged.constEnd()) { if (changedIt.value()) { active.append(changedIt.key()); } else { active.removeAll(changedIt.key()); } changedIt++; } - m_widgetFactory->setNoLinks(true); m_widgetFactory->setReadOnly(true); m_gridLayout->setColumnStretch(0, 1); m_gridLayout->setColumnStretch(1, 3); m_gridLayout->setColumnStretch(2, 0); m_gridLayout->setColumnStretch(3, 6); } else { data = m_filter->filter(data); - m_widgetFactory->setNoLinks( m_provider->realTimeIndexing() ); m_widgetFactory->setReadOnly(m_provider->isReadOnly()); m_gridLayout->setColumnStretch(0, 4); m_gridLayout->setColumnStretch(1, 0); m_gridLayout->setColumnStretch(2, 6); m_gridLayout->setColumnStretch(3, 0); } int rowIndex = 0; // Iterate through all remaining items. // Embed the label and the value as new row in the widget const QStringList keys = sortedKeys(data); const int spacerWidth = QFontMetrics(q->font()).size(Qt::TextSingleLine, QStringLiteral(" ")).width(); const int labelColumn = m_configureVisibleProperties ? 1 : 0; for (const auto& key : keys) { Row row; if (m_configureVisibleProperties) { row.checkBox = new QCheckBox(q); if (active.contains(key)) { row.checkBox->setChecked(true); } m_gridLayout->addWidget(row.checkBox, rowIndex, 0, Qt::AlignTop | Qt::AlignRight); connect(row.checkBox, &QCheckBox::stateChanged, q, [this, key](int state) { this->m_visibilityChanged[key] = (state == Qt::Checked); }); } else { row.checkBox = nullptr; } row.label = createLabel(key, m_provider->label(key), q); m_gridLayout->addWidget(row.label, rowIndex, labelColumn + 0, Qt::AlignRight); m_gridLayout->addItem(new QSpacerItem(spacerWidth, 1), rowIndex, labelColumn + 1); row.value = m_widgetFactory->createWidget(key, data[key], q); m_gridLayout->addWidget(row.value, rowIndex, labelColumn + 2, Qt::AlignLeft); m_gridLayout->setRowStretch(rowIndex, 0); // Remember the label and value-widget as row m_rows.append(row); ++rowIndex; } // Add vertical stretch - when the widget is embedded with extra vertical // space, it should be added at the bottom, not distributed between the // items. m_gridLayout->addItem(new QSpacerItem(0, 0), rowIndex, 0, 1, -1); m_gridLayout->setRowStretch(rowIndex, 1); q->updateGeometry(); emit q->metaDataRequestFinished(m_provider->items()); } void FileMetaDataWidget::Private::slotLinkActivated(const QString& link) { const QUrl url = QUrl::fromUserInput(link); if (url.isValid()) { emit q->urlActivated(url); } } void FileMetaDataWidget::Private::slotDataChangeStarted() { q->setEnabled(false); } void FileMetaDataWidget::Private::slotDataChangeFinished() { q->setEnabled(true); } QStringList FileMetaDataWidget::Private::sortedKeys(const QVariantMap& data) const { // Create a map, where the translated label prefixed with the // sort priority acts as key. The data of each entry is the URI // of the data. By this the all URIs are sorted by the sort priority // and sub sorted by the translated labels. QMap map; QVariantMap::const_iterator hashIt = data.constBegin(); while (hashIt != data.constEnd()) { const QString propName = hashIt.key(); QString key = m_provider->group(propName); key += m_provider->label(propName); map.insertMulti(key, propName); ++hashIt; } // Apply the URIs from the map to the list that will get returned. // The list will then be alphabetically ordered by the translated labels of the URIs. QStringList list; QMap::const_iterator mapIt = map.constBegin(); while (mapIt != map.constEnd()) { list.append(mapIt.value()); ++mapIt; } return list; } void FileMetaDataWidget::Private::saveConfig() { if (m_visibilityChanged.isEmpty()) { return; } KConfig config(QStringLiteral("baloofileinformationrc"), KConfig::NoGlobals); KConfigGroup showGroup = config.group("Show"); auto changedIt = m_visibilityChanged.constBegin(); while (changedIt != m_visibilityChanged.constEnd()) { showGroup.writeEntry(changedIt.key(), changedIt.value()); changedIt++; } showGroup.sync(); } FileMetaDataWidget::FileMetaDataWidget(QWidget* parent) : QWidget(parent) , d(new Private(this)) { } FileMetaDataWidget::~FileMetaDataWidget() { delete d; } void FileMetaDataWidget::setItems(const KFileItemList& items) { d->m_provider->setItems(items); d->m_widgetFactory->setItems(items); } KFileItemList FileMetaDataWidget::items() const { return d->m_provider->items(); } void FileMetaDataWidget::setReadOnly(bool readOnly) { d->m_provider->setReadOnly(readOnly); d->m_widgetFactory->setReadOnly(readOnly); } bool FileMetaDataWidget::isReadOnly() const { return d->m_provider->isReadOnly(); } void FileMetaDataWidget::setDateFormat(const DateFormats format) { d->m_widgetFactory->setDateFormat(format); } DateFormats FileMetaDataWidget::dateFormat() const { return d->m_widgetFactory->dateFormat(); } QSize FileMetaDataWidget::sizeHint() const { if (d->m_gridLayout == nullptr) { return QWidget::sizeHint(); } // Calculate the required width for the labels and values int leftWidthMax = 0; int rightWidthMax = 0; int rightWidthAverage = 0; for (const Private::Row& row : qAsConst(d->m_rows)) { const QWidget* valueWidget = row.value; const int rightWidth = valueWidget->sizeHint().width(); rightWidthAverage += rightWidth; if (rightWidth > rightWidthMax) { rightWidthMax = rightWidth; } const int leftWidth = row.label->sizeHint().width(); if (leftWidth > leftWidthMax) { leftWidthMax = leftWidth; } } // Some value widgets might return a very huge width for the size hint. // Limit the maximum width to the double width of the overall average // to assure a less messed layout. if (d->m_rows.count() > 1) { rightWidthAverage /= d->m_rows.count(); if (rightWidthMax > rightWidthAverage * 2) { rightWidthMax = rightWidthAverage * 2; } } // Based on the available width calculate the required height int height = d->m_gridLayout->margin() * 2 + d->m_gridLayout->spacing() * (d->m_rows.count() - 1); for (const Private::Row& row : qAsConst(d->m_rows)) { const QWidget* valueWidget = row.value; const int rowHeight = qMax(row.label->heightForWidth(leftWidthMax), valueWidget->heightForWidth(rightWidthMax)); height += rowHeight; } const int width = d->m_gridLayout->margin() * 2 + leftWidthMax + d->m_gridLayout->spacing() + rightWidthMax; return QSize(width, height); } void FileMetaDataWidget::setConfigurationMode(ConfigurationMode mode) { if (mode == ConfigurationMode::ReStart) { d->m_configureVisibleProperties = true; } else if (mode == ConfigurationMode::Accept) { d->saveConfig(); d->m_configureVisibleProperties = false; } else if (mode == ConfigurationMode::Cancel) { d->m_configureVisibleProperties = false; } d->m_visibilityChanged.clear(); d->slotLoadingFinished(); } #include "moc_filemetadatawidget.cpp" diff --git a/src/widgetfactory.cpp b/src/widgetfactory.cpp index 9746ef2..fd7c882 100644 --- a/src/widgetfactory.cpp +++ b/src/widgetfactory.cpp @@ -1,371 +1,359 @@ /* Copyright (C) 2012-2014 Vishesh Handa Code largely copied/adapted from KFileMetadataProvider Copyright (C) 2010 by Peter Penz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "widgetfactory.h" #include "tagwidget.h" #include "kcommentwidget_p.h" #include "KRatingWidget" #include #include #include #include #include #include #include #include #include #include #include namespace { static QString plainText(const QString& richText) { QString plainText; plainText.reserve(richText.length()); bool skip = false; for (int i = 0; i < richText.length(); ++i) { const QChar c = richText.at(i); if (c == QLatin1Char('<')) { skip = true; } else if (c == QLatin1Char('>')) { skip = false; } else if (!skip) { plainText.append(c); } } return plainText; } } using namespace Baloo; WidgetFactory::WidgetFactory(QObject* parent) : QObject(parent) , m_readOnly( false ) - , m_noLinks( false ) , m_dateFormat(QLocale::LongFormat) { } WidgetFactory::~WidgetFactory() { } // // Widget Creation // static QString formatDateTime(const QVariant& value, QLocale::FormatType dateFormat) { const QString valueString = value.toString(); QDateTime dt = QDateTime::fromString(valueString, Qt::ISODate); if (dt.isValid()) { KFormat form; QTime time = dt.time(); // Check if Date/DateTime if (!time.hour() && !time.minute() && !time.second()){ return form.formatRelativeDate(dt.date(), dateFormat); } else { return form.formatRelativeDateTime(dt, dateFormat); } } return valueString; } static QString toString(const QVariant& value, QLocale::FormatType dateFormat) { switch (value.type()) { case QVariant::Int: return QLocale().toString(value.toInt()); case QVariant::Double: return QLocale().toString(value.toDouble()); case QVariant::StringList: return value.toStringList().join(i18nc("String list separator", ", ")); case QVariant::Date: case QVariant::DateTime: { return formatDateTime(value, dateFormat); } case QVariant::List: { QStringList list; for (const QVariant& var : value.toList()) { list << toString(var, dateFormat); } return list.join(i18nc("String list separator", ", ")); } default: return value.toString(); } } QWidget* WidgetFactory::createWidget(const QString& prop, const QVariant& value, QWidget* parent) { QWidget* widget = nullptr; const int maxUrlLength = 80; if (prop == QLatin1String("rating")) { widget = createRatingWidget( value.toInt(), parent ); } else if (prop == QLatin1String("userComment")) { widget = createCommentWidget( value.toString(), parent ); } else if (prop == QLatin1String("tags")) { QStringList tags = value.toStringList(); QCollator coll; coll.setNumericMode(true); std::sort(tags.begin(), tags.end(), [&](const QString& s1, const QString& s2){ return coll.compare(s1, s2) < 0; }); widget = createTagWidget( tags, parent ); } else { QString valueString; auto pi = KFileMetaData::PropertyInfo::fromName(prop); if (pi.name() == QLatin1String("originUrl")) { - //Won't make sense to shrink originUrl with noLinks, - //since it would make original URL unobtainable valueString = value.toString(); - if (!m_noLinks) { - //Shrink link name. - auto labelString = valueString; - if (labelString.size() > maxUrlLength) { - labelString = KStringHandler::csqueeze(labelString, maxUrlLength); - } - valueString = QStringLiteral("%2").arg(valueString, labelString); - } + //Shrink link label + auto labelString = KStringHandler::csqueeze(valueString, maxUrlLength); + valueString = QStringLiteral("%2").arg(valueString, labelString); + } else if (pi.name() != QLatin1String("empty")) { if (pi.valueType() == QVariant::DateTime || pi.valueType() == QVariant::Date) { valueString = formatDateTime(value, m_dateFormat); } else { valueString = pi.formatAsDisplayString(value); } } else { valueString = toString(value, m_dateFormat); } widget = createValueWidget(valueString, parent); } widget->setForegroundRole(parent->foregroundRole()); widget->setFont(parent->font()); widget->setObjectName(prop); return widget; } QWidget* WidgetFactory::createTagWidget(const QStringList& tags, QWidget* parent) { TagWidget* tagWidget = new TagWidget(parent); tagWidget->setReadyOnly(m_readOnly); tagWidget->setSelectedTags(tags); connect(tagWidget, &TagWidget::selectionChanged, this, &WidgetFactory::slotTagsChanged); connect(tagWidget, &TagWidget::tagClicked, this, &WidgetFactory::slotTagClicked); m_tagWidget = tagWidget; m_prevTags = tags; return tagWidget; } QWidget* WidgetFactory::createCommentWidget(const QString& comment, QWidget* parent) { KCommentWidget* commentWidget = new KCommentWidget(parent); commentWidget->setText(comment); commentWidget->setReadOnly(m_readOnly); connect(commentWidget, &KCommentWidget::commentChanged, this, &WidgetFactory::slotCommentChanged); m_commentWidget = commentWidget; return commentWidget; } QWidget* WidgetFactory::createRatingWidget(int rating, QWidget* parent) { KRatingWidget* ratingWidget = new KRatingWidget(parent); const Qt::Alignment align = (ratingWidget->layoutDirection() == Qt::LeftToRight) ? Qt::AlignLeft : Qt::AlignRight; ratingWidget->setAlignment(align); ratingWidget->setRating(rating); const QFontMetrics metrics(parent->font()); ratingWidget->setPixmapSize(metrics.height()); connect(ratingWidget, static_cast(&KRatingWidget::ratingChanged), this, &WidgetFactory::slotRatingChanged); m_ratingWidget = ratingWidget; return ratingWidget; } // The default size hint of QLabel tries to return a square size. // This does not work well in combination with layouts that use // heightForWidth(): In this case it is possible that the content // of a label might get clipped. By specifying a size hint // with a maximum width that is necessary to contain the whole text, // using heightForWidth() assures having a non-clipped text. class ValueWidget : public QLabel { public: explicit ValueWidget(QWidget* parent = nullptr); QSize sizeHint() const override; }; ValueWidget::ValueWidget(QWidget* parent) : QLabel(parent) { } QSize ValueWidget::sizeHint() const { QFontMetrics metrics(font()); // TODO: QLabel internally provides already a method sizeForWidth(), // that would be sufficient. However this method is not accessible, so // as workaround the tags from a richtext are removed manually here to // have a proper size hint. return metrics.size(Qt::TextSingleLine, plainText(text())); } QWidget* WidgetFactory::createValueWidget(const QString& value, QWidget* parent) { ValueWidget* valueWidget = new ValueWidget(parent); valueWidget->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); valueWidget->setWordWrap(true); valueWidget->setAlignment(Qt::AlignTop | Qt::AlignLeft); valueWidget->setText(m_readOnly ? plainText(value) : value); connect(valueWidget, &ValueWidget::linkActivated, this, &WidgetFactory::slotLinkActivated); return valueWidget; } // // Data Synchronization // void WidgetFactory::slotCommentChanged(const QString& comment) { for (const KFileItem& item : qAsConst(m_items)) { QUrl url = item.targetUrl(); if (!url.isLocalFile()) { continue; } KFileMetaData::UserMetaData md(url.toLocalFile()); md.setUserComment(comment); } emit dataChangeStarted(); emit dataChangeFinished(); } void WidgetFactory::slotRatingChanged(uint rating) { for (const KFileItem& item : qAsConst(m_items)) { QUrl url = item.targetUrl(); if (!url.isLocalFile()) { continue; } KFileMetaData::UserMetaData md(url.toLocalFile()); md.setRating(rating); } emit dataChangeStarted(); emit dataChangeFinished(); } void WidgetFactory::slotTagsChanged(const QStringList& tags) { if (m_tagWidget) { for (const KFileItem& item : qAsConst(m_items)) { QUrl url = item.targetUrl(); if (!url.isLocalFile()) { continue; } KFileMetaData::UserMetaData md(url.toLocalFile()); // When multiple tags are selected one doesn't want to loose the old tags // of any of the resources. Unless specifically removed. QStringList newTags = md.tags() + tags; newTags.removeDuplicates(); for (const QString& tag : m_prevTags) { if (!tags.contains(tag)) { newTags.removeAll(tag); } } md.setTags(newTags); } m_prevTags = tags; emit dataChangeStarted(); emit dataChangeFinished(); } } // // Notifications // void WidgetFactory::slotLinkActivated(const QString& url) { emit urlActivated(QUrl::fromUserInput(url)); } void WidgetFactory::slotTagClicked(const QString& tag) { QUrl url; url.setScheme(QStringLiteral("tags")); url.setPath(tag); emit urlActivated(url); } // // Accessor Methods // void WidgetFactory::setReadOnly(bool value) { m_readOnly = value; } -void WidgetFactory::setNoLinks(bool value) -{ - m_noLinks = value; -} - void WidgetFactory::setItems(const KFileItemList& items) { m_items = items; } Baloo::DateFormats WidgetFactory::dateFormat() const { return static_cast(m_dateFormat); } void Baloo::WidgetFactory::setDateFormat(const Baloo::DateFormats format) { m_dateFormat = static_cast(format); } diff --git a/src/widgetfactory.h b/src/widgetfactory.h index 5f3cd0e..20abc01 100644 --- a/src/widgetfactory.h +++ b/src/widgetfactory.h @@ -1,87 +1,85 @@ /* Copyright (C) 2012 Vishesh Handa This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef WIDGETFACTORY_H #define WIDGETFACTORY_H #include "filemetadatawidget.h" #include #include class KJob; class QUrl; class KCommentWidget; class KRatingWidget; namespace Baloo { class Tag; class TagWidget; class WidgetFactory : public QObject { Q_OBJECT public: explicit WidgetFactory(QObject* parent = nullptr); ~WidgetFactory() override; void setItems(const KFileItemList& items); void setReadOnly(bool value); - void setNoLinks(bool value); void setDateFormat(const DateFormats format); DateFormats dateFormat() const; QWidget* createWidget(const QString& prop, const QVariant& value, QWidget* parent); Q_SIGNALS: void urlActivated(const QUrl& url); void dataChangeStarted(); void dataChangeFinished(); private Q_SLOTS: void slotTagsChanged(const QStringList& tags); void slotCommentChanged(const QString& comment); void slotRatingChanged(uint rating); void slotTagClicked(const QString& tag); void slotLinkActivated(const QString& url); private: QWidget* createRatingWidget(int rating, QWidget* parent); QWidget* createTagWidget(const QStringList& tags, QWidget* parent); QWidget* createCommentWidget(const QString& comment, QWidget* parent); QWidget* createValueWidget(const QString& value, QWidget* parent); TagWidget* m_tagWidget; KRatingWidget* m_ratingWidget; KCommentWidget* m_commentWidget; KFileItemList m_items; QStringList m_prevTags; bool m_readOnly; - bool m_noLinks; QLocale::FormatType m_dateFormat; }; } #endif // WIDGETFACTORY_H