File indexing completed on 2024-04-21 04:39:57

0001 /*
0002     SPDX-FileCopyrightText: 2012-2014 Vishesh Handa <vhanda@kde.org>
0003 
0004     Code largely copied/adapted from KFileMetadataProvider
0005     SPDX-FileCopyrightText: 2010 Peter Penz <peter.penz@gmx.at>
0006 
0007     SPDX-License-Identifier: LGPL-2.1-or-later
0008 */
0009 
0010 #include "widgetfactory.h"
0011 #include "KRatingWidget"
0012 #include "filemetadatautil_p.h"
0013 #include "kcommentwidget_p.h"
0014 #include "tagwidget.h"
0015 
0016 #include <KFileMetaData/PropertyInfo>
0017 #include <KFileMetaData/UserMetaData>
0018 
0019 #include <QCollator>
0020 #include <QLabel>
0021 #include <QLocale>
0022 #include <QTime>
0023 #include <QUrl>
0024 
0025 #include <KApplicationTrader>
0026 #include <KFormat>
0027 #include <KLocalizedString>
0028 #include <KStringHandler>
0029 
0030 using namespace Baloo;
0031 
0032 WidgetFactory::WidgetFactory(QObject *parent)
0033     : QObject(parent)
0034     , m_dateFormat(QLocale::LongFormat)
0035 {
0036 }
0037 
0038 WidgetFactory::~WidgetFactory() = default;
0039 
0040 //
0041 // Widget Creation
0042 //
0043 static QString formatDateTime(const QVariant &value, QLocale::FormatType dateFormat)
0044 {
0045     const QString valueString = value.toString();
0046     QDateTime dt = QDateTime::fromString(valueString, Qt::ISODate);
0047 
0048     if (dt.isValid()) {
0049         KFormat form;
0050         QTime time = dt.time();
0051         // Check if Date/DateTime
0052         if (!time.hour() && !time.minute() && !time.second()) {
0053             return form.formatRelativeDate(dt.date(), dateFormat);
0054         } else {
0055             return form.formatRelativeDateTime(dt, dateFormat);
0056         }
0057     }
0058 
0059     return valueString;
0060 }
0061 
0062 static QString toString(const QVariant &value, QLocale::FormatType dateFormat)
0063 {
0064     switch (value.type()) {
0065     case QVariant::Int:
0066         return QLocale().toString(value.toInt());
0067     case QVariant::Double:
0068         return QLocale().toString(value.toDouble());
0069     case QVariant::StringList:
0070         return value.toStringList().join(i18nc("String list separator", ", "));
0071     case QVariant::Date:
0072     case QVariant::DateTime: {
0073         return formatDateTime(value, dateFormat);
0074     }
0075     case QVariant::List: {
0076         QStringList list;
0077         const auto valueList = value.toList();
0078         for (const QVariant &var : valueList) {
0079             list << toString(var, dateFormat);
0080         }
0081         return list.join(i18nc("String list separator", ", "));
0082     }
0083 
0084     default:
0085         return value.toString();
0086     }
0087 }
0088 
0089 QWidget *WidgetFactory::createWidget(const QString &prop, const QVariant &value, QWidget *parent)
0090 {
0091     QWidget *widget = nullptr;
0092     const int maxUrlLength = 80;
0093 
0094     if (prop == QLatin1String("rating")) {
0095         widget = createRatingWidget(value.toInt(), parent);
0096     } else if (prop == QLatin1String("userComment")) {
0097         widget = createCommentWidget(value.toString(), parent);
0098     } else if (prop == QLatin1String("tags")) {
0099         widget = createTagWidget(Baloo::Private::sortTags(value.toStringList()), parent);
0100     } else if (prop == QLatin1String("gpsLocation")) {
0101         const auto pair = value.value<QPair<float, float>>();
0102         const auto latitude = pair.first;
0103         const auto longitude = pair.second;
0104 
0105         const QString latitudeStr = latitude < 0 ? i18nc("Latitude (South)", "%1°S", -latitude) : i18nc("Latitude (North)", "%1°N", latitude);
0106         const QString longitudeStr = longitude < 0 ? i18nc("Longitude (West)", "%1°W", -longitude) : i18nc("Longitude (East)", "%1°E", longitude);
0107         const QString gpsLocationStr = latitudeStr + QLatin1Char(' ') + longitudeStr;
0108 
0109         if (const auto geoService = KApplicationTrader::preferredService(QStringLiteral("x-scheme-handler/geo"))) {
0110             const QString geoUri = QStringLiteral("geo:%1,%2").arg(latitude).arg(longitude);
0111             QLabel *valueWidget = createLinkWidget(parent);
0112 
0113             valueWidget->setText(QStringLiteral("<a href='%1'>%2</a>").arg(geoUri, gpsLocationStr));
0114             valueWidget->setToolTip(i18nc("@info:tooltip Show location in map viewer", "Show location in %1", geoService->name()));
0115             widget = valueWidget;
0116         } else {
0117             QLabel *valueWidget = createValueWidget(parent);
0118             valueWidget->setText(gpsLocationStr);
0119             widget = valueWidget;
0120         }
0121 
0122     } else if (prop == QLatin1String("originUrl")) {
0123         QLabel *valueWidget = createLinkWidget(parent);
0124         QString valueString = value.toUrl().toString();
0125         // Shrink link label
0126         auto labelString = KStringHandler::csqueeze(valueString, maxUrlLength);
0127         valueString = QStringLiteral("<a href=\"%1\">%2</a>").arg(valueString, labelString);
0128         valueWidget->setText(valueString);
0129         widget = valueWidget;
0130 
0131     } else {
0132         QString valueString;
0133         QLabel *valueWidget = createValueWidget(parent);
0134 
0135         auto pi = KFileMetaData::PropertyInfo::fromName(prop);
0136         if (pi.property() != KFileMetaData::Property::Empty) {
0137             if (pi.valueType() == QVariant::DateTime || pi.valueType() == QVariant::Date) {
0138                 valueString = formatDateTime(value, m_dateFormat);
0139             } else {
0140                 valueString = pi.formatAsDisplayString(value);
0141             }
0142         } else {
0143             valueString = toString(value, m_dateFormat);
0144         }
0145 
0146         valueWidget->setText(valueString);
0147         widget = valueWidget;
0148     }
0149 
0150     widget->setForegroundRole(parent->foregroundRole());
0151     widget->setFont(parent->font());
0152     widget->setObjectName(prop);
0153     return widget;
0154 }
0155 
0156 QWidget *WidgetFactory::createTagWidget(const QStringList &tags, QWidget *parent)
0157 {
0158     auto tagWidget = new TagWidget(parent);
0159     tagWidget->setReadyOnly(m_readOnly);
0160     tagWidget->setSelectedTags(tags);
0161 
0162     connect(tagWidget, &TagWidget::selectionChanged, this, &WidgetFactory::slotTagsChanged);
0163     connect(tagWidget, &TagWidget::tagClicked, this, [this](const QString &tag) {
0164         QUrl url;
0165         url.setScheme(QStringLiteral("tags"));
0166         url.setPath(tag);
0167         Q_EMIT urlActivated(url);
0168     });
0169 
0170     m_tagWidget = tagWidget;
0171     m_prevTags = tags;
0172 
0173     return tagWidget;
0174 }
0175 
0176 QWidget *WidgetFactory::createCommentWidget(const QString &comment, QWidget *parent)
0177 {
0178     auto commentWidget = new KCommentWidget(parent);
0179     commentWidget->setText(comment);
0180     commentWidget->setReadOnly(m_readOnly);
0181 
0182     connect(commentWidget, &KCommentWidget::commentChanged, this, &WidgetFactory::slotCommentChanged);
0183 
0184     m_commentWidget = commentWidget;
0185 
0186     return commentWidget;
0187 }
0188 
0189 QWidget *WidgetFactory::createRatingWidget(int rating, QWidget *parent)
0190 {
0191     auto ratingWidget = new KRatingWidget(parent);
0192     const Qt::Alignment align = (ratingWidget->layoutDirection() == Qt::LeftToRight) ? Qt::AlignLeft : Qt::AlignRight;
0193     ratingWidget->setAlignment(align);
0194     ratingWidget->setRating(rating);
0195     const QFontMetrics metrics(parent->font());
0196     ratingWidget->setPixmapSize(metrics.height());
0197 
0198     connect(ratingWidget, static_cast<void (KRatingWidget::*)(int)>(&KRatingWidget::ratingChanged), this, &WidgetFactory::slotRatingChanged);
0199 
0200     m_ratingWidget = ratingWidget;
0201 
0202     return ratingWidget;
0203 }
0204 
0205 // The default size hint of QLabel tries to return a square size.
0206 // This does not work well in combination with layouts that use
0207 // heightForWidth(): In this case it is possible that the content
0208 // of a label might get clipped. By specifying a size hint
0209 // with a maximum width that is necessary to contain the whole text,
0210 // using heightForWidth() assures having a non-clipped text.
0211 class ValueWidget : public QLabel
0212 {
0213 public:
0214     explicit ValueWidget(QWidget *parent = nullptr);
0215     QSize sizeHint() const override;
0216 };
0217 
0218 ValueWidget::ValueWidget(QWidget *parent)
0219     : QLabel(parent)
0220 {
0221 }
0222 
0223 QSize ValueWidget::sizeHint() const
0224 {
0225     QFontMetrics metrics(font());
0226     // TODO: QLabel internally provides already a method sizeForWidth(),
0227     // that would be sufficient. However this method is not accessible.
0228     return metrics.size(Qt::TextSingleLine, text());
0229 }
0230 
0231 QLabel *WidgetFactory::createValueWidget(QWidget *parent)
0232 {
0233     auto valueWidget = new ValueWidget(parent);
0234     valueWidget->setWordWrap(true);
0235     valueWidget->setAlignment(Qt::AlignTop | Qt::AlignLeft);
0236 
0237     valueWidget->setTextInteractionFlags(Qt::TextSelectableByMouse);
0238     valueWidget->setTextFormat(Qt::PlainText);
0239 
0240     return valueWidget;
0241 }
0242 
0243 QLabel *WidgetFactory::createLinkWidget(QWidget *parent)
0244 {
0245     auto valueWidget = new ValueWidget(parent);
0246     valueWidget->setWordWrap(true);
0247     valueWidget->setAlignment(Qt::AlignTop | Qt::AlignLeft);
0248 
0249     valueWidget->setTextInteractionFlags(Qt::TextBrowserInteraction);
0250     valueWidget->setTextFormat(Qt::RichText);
0251     connect(valueWidget, &ValueWidget::linkActivated, this, [this](const QString &url) {
0252         Q_EMIT this->urlActivated(QUrl::fromUserInput(url));
0253     });
0254 
0255     return valueWidget;
0256 }
0257 
0258 //
0259 // Data Synchronization
0260 //
0261 
0262 void WidgetFactory::slotCommentChanged(const QString &comment)
0263 {
0264     for (const KFileItem &item : std::as_const(m_items)) {
0265         QUrl url = item.targetUrl();
0266         if (!url.isLocalFile()) {
0267             continue;
0268         }
0269         KFileMetaData::UserMetaData md(url.toLocalFile());
0270         md.setUserComment(comment);
0271     }
0272 }
0273 
0274 void WidgetFactory::slotRatingChanged(int rating)
0275 {
0276     for (const KFileItem &item : std::as_const(m_items)) {
0277         QUrl url = item.targetUrl();
0278         if (!url.isLocalFile()) {
0279             continue;
0280         }
0281         KFileMetaData::UserMetaData md(url.toLocalFile());
0282         md.setRating(rating);
0283     }
0284 }
0285 
0286 void WidgetFactory::slotTagsChanged(const QStringList &tags)
0287 {
0288     if (m_tagWidget) {
0289         for (const KFileItem &item : std::as_const(m_items)) {
0290             QUrl url = item.targetUrl();
0291             if (!url.isLocalFile()) {
0292                 continue;
0293             }
0294             KFileMetaData::UserMetaData md(url.toLocalFile());
0295 
0296             // When multiple tags are selected one doesn't want to loose the old tags
0297             // of any of the resources. Unless specifically removed.
0298             QStringList newTags = md.tags() + tags;
0299             newTags.removeDuplicates();
0300 
0301             for (const QString &tag : std::as_const(m_prevTags)) {
0302                 if (!tags.contains(tag)) {
0303                     newTags.removeAll(tag);
0304                 }
0305             }
0306             md.setTags(newTags);
0307         }
0308 
0309         m_prevTags = tags;
0310     }
0311 }
0312 
0313 //
0314 // Accessor Methods
0315 //
0316 void WidgetFactory::setReadOnly(bool value)
0317 {
0318     m_readOnly = value;
0319 }
0320 
0321 void WidgetFactory::setItems(const KFileItemList &items)
0322 {
0323     m_items = items;
0324 }
0325 
0326 Baloo::DateFormats WidgetFactory::dateFormat() const
0327 {
0328     return static_cast<Baloo::DateFormats>(m_dateFormat);
0329 }
0330 
0331 void Baloo::WidgetFactory::setDateFormat(const Baloo::DateFormats format)
0332 {
0333     m_dateFormat = static_cast<QLocale::FormatType>(format);
0334 }
0335 
0336 #include "moc_widgetfactory.cpp"