Baloo Widgets

widgetfactory.cpp
1 /*
2  SPDX-FileCopyrightText: 2012-2014 Vishesh Handa <[email protected]>
3 
4  Code largely copied/adapted from KFileMetadataProvider
5  SPDX-FileCopyrightText: 2010 Peter Penz <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.1-or-later
8 */
9 
10 #include "widgetfactory.h"
11 #include "KRatingWidget"
12 #include "kcommentwidget_p.h"
13 #include "tagwidget.h"
14 
15 #include <KFileMetaData/PropertyInfo>
16 #include <KFileMetaData/UserMetaData>
17 
18 #include <QCollator>
19 #include <QLabel>
20 #include <QLocale>
21 #include <QTime>
22 #include <QUrl>
23 
24 #include <KApplicationTrader>
25 #include <KFormat>
26 #include <KJob>
27 #include <KLocalizedString>
28 #include <KStringHandler>
29 
30 using namespace Baloo;
31 
32 WidgetFactory::WidgetFactory(QObject *parent)
33  : QObject(parent)
34  , m_dateFormat(QLocale::LongFormat)
35 {
36 }
37 
38 WidgetFactory::~WidgetFactory() = default;
39 
40 //
41 // Widget Creation
42 //
43 static QString formatDateTime(const QVariant &value, QLocale::FormatType dateFormat)
44 {
45  const QString valueString = value.toString();
46  QDateTime dt = QDateTime::fromString(valueString, Qt::ISODate);
47 
48  if (dt.isValid()) {
49  KFormat form;
50  QTime time = dt.time();
51  // Check if Date/DateTime
52  if (!time.hour() && !time.minute() && !time.second()) {
53  return form.formatRelativeDate(dt.date(), dateFormat);
54  } else {
55  return form.formatRelativeDateTime(dt, dateFormat);
56  }
57  }
58 
59  return valueString;
60 }
61 
62 static QString toString(const QVariant &value, QLocale::FormatType dateFormat)
63 {
64  switch (value.type()) {
65  case QVariant::Int:
66  return QLocale().toString(value.toInt());
67  case QVariant::Double:
68  return QLocale().toString(value.toDouble());
70  return value.toStringList().join(i18nc("String list separator", ", "));
71  case QVariant::Date:
72  case QVariant::DateTime: {
73  return formatDateTime(value, dateFormat);
74  }
75  case QVariant::List: {
77  const auto valueList = value.toList();
78  for (const QVariant &var : valueList) {
79  list << toString(var, dateFormat);
80  }
81  return list.join(i18nc("String list separator", ", "));
82  }
83 
84  default:
85  return value.toString();
86  }
87 }
88 
89 QWidget *WidgetFactory::createWidget(const QString &prop, const QVariant &value, QWidget *parent)
90 {
91  QWidget *widget = nullptr;
92  const int maxUrlLength = 80;
93 
94  if (prop == QLatin1String("rating")) {
95  widget = createRatingWidget(value.toInt(), parent);
96  } else if (prop == QLatin1String("userComment")) {
97  widget = createCommentWidget(value.toString(), parent);
98  } else if (prop == QLatin1String("tags")) {
99  QStringList tags = value.toStringList();
100  QCollator coll;
101  coll.setNumericMode(true);
102  std::sort(tags.begin(), tags.end(), [&](const QString &s1, const QString &s2) {
103  return coll.compare(s1, s2) < 0;
104  });
105  widget = createTagWidget(tags, parent);
106  } else if (prop == QLatin1String("gpsLocation")) {
107  const auto pair = value.value<QPair<float, float>>();
108  const auto latitude = pair.first;
109  const auto longitude = pair.second;
110 
111  const QString geoUri = QStringLiteral("geo:%1,%2").arg(latitude).arg(longitude);
112 
113  const QString latitudeStr = latitude < 0 ? i18nc("Latitude (South)", "%1°S", -latitude) : i18nc("Latitude (North)", "%1°N", latitude);
114  const QString longitudeStr = longitude < 0 ? i18nc("Longitude (West)", "%1°W", -longitude) : i18nc("Longitude (East)", "%1°E", longitude);
115  const QString gpsLocationStr = latitudeStr + QLatin1Char(' ') + longitudeStr;
116 
117  QLabel *valueWidget = createValueWidget(parent);
118 
119  if (const auto geoService = KApplicationTrader::preferredService(QStringLiteral("x-scheme-handler/geo"))) {
120  valueWidget->setTextFormat(Qt::RichText);
122  valueWidget->setText(QStringLiteral("<a href='%1'>%2</a>").arg(geoUri, gpsLocationStr));
123  valueWidget->setToolTip(i18nc("@info:tooltip Show location in map viewer", "Show location in %1", geoService->name()));
124  } else {
125  valueWidget->setText(gpsLocationStr);
126  }
127 
128  widget = valueWidget;
129  } else {
130  QString valueString;
131  QLabel *valueWidget = createValueWidget(parent);
132 
134  if (pi.name() == QLatin1String("originUrl")) {
135  auto url = value.toUrl();
136  valueString = url.toString();
137  // Shrink link label
138  auto labelString = KStringHandler::csqueeze(valueString, maxUrlLength);
139  valueString = QStringLiteral("<a href=\"%1\">%2</a>").arg(valueString, labelString);
140  valueWidget->setTextFormat(Qt::RichText);
142 
143  } else if (pi.name() != QLatin1String("empty")) {
144  if (pi.valueType() == QVariant::DateTime || pi.valueType() == QVariant::Date) {
145  valueString = formatDateTime(value, m_dateFormat);
146  } else {
147  valueString = pi.formatAsDisplayString(value);
148  }
149  } else {
150  valueString = toString(value, m_dateFormat);
151  }
152 
153  valueWidget->setText(valueString);
154  widget = valueWidget;
155  }
156 
157  widget->setForegroundRole(parent->foregroundRole());
158  widget->setFont(parent->font());
159  widget->setObjectName(prop);
160  return widget;
161 }
162 
163 QWidget *WidgetFactory::createTagWidget(const QStringList &tags, QWidget *parent)
164 {
165  auto tagWidget = new TagWidget(parent);
166  tagWidget->setReadyOnly(m_readOnly);
167  tagWidget->setSelectedTags(tags);
168 
169  connect(tagWidget, &TagWidget::selectionChanged, this, &WidgetFactory::slotTagsChanged);
170  connect(tagWidget, &TagWidget::tagClicked, this, &WidgetFactory::slotTagClicked);
171 
172  m_tagWidget = tagWidget;
173  m_prevTags = tags;
174 
175  return tagWidget;
176 }
177 
178 QWidget *WidgetFactory::createCommentWidget(const QString &comment, QWidget *parent)
179 {
180  auto commentWidget = new KCommentWidget(parent);
181  commentWidget->setText(comment);
182  commentWidget->setReadOnly(m_readOnly);
183 
184  connect(commentWidget, &KCommentWidget::commentChanged, this, &WidgetFactory::slotCommentChanged);
185 
186  m_commentWidget = commentWidget;
187 
188  return commentWidget;
189 }
190 
191 QWidget *WidgetFactory::createRatingWidget(int rating, QWidget *parent)
192 {
193  auto ratingWidget = new KRatingWidget(parent);
194  const Qt::Alignment align = (ratingWidget->layoutDirection() == Qt::LeftToRight) ? Qt::AlignLeft : Qt::AlignRight;
195  ratingWidget->setAlignment(align);
196  ratingWidget->setRating(rating);
197  const QFontMetrics metrics(parent->font());
198  ratingWidget->setPixmapSize(metrics.height());
199 
200  connect(ratingWidget, static_cast<void (KRatingWidget::*)(int)>(&KRatingWidget::ratingChanged), this, &WidgetFactory::slotRatingChanged);
201 
202  m_ratingWidget = ratingWidget;
203 
204  return ratingWidget;
205 }
206 
207 // The default size hint of QLabel tries to return a square size.
208 // This does not work well in combination with layouts that use
209 // heightForWidth(): In this case it is possible that the content
210 // of a label might get clipped. By specifying a size hint
211 // with a maximum width that is necessary to contain the whole text,
212 // using heightForWidth() assures having a non-clipped text.
213 class ValueWidget : public QLabel
214 {
215 public:
216  explicit ValueWidget(QWidget *parent = nullptr);
217  QSize sizeHint() const override;
218 };
219 
220 ValueWidget::ValueWidget(QWidget *parent)
221  : QLabel(parent)
222 {
223 }
224 
226 {
227  QFontMetrics metrics(font());
228  // TODO: QLabel internally provides already a method sizeForWidth(),
229  // that would be sufficient. However this method is not accessible.
230  return metrics.size(Qt::TextSingleLine, text());
231 }
232 
233 QLabel *WidgetFactory::createValueWidget(QWidget *parent)
234 {
235  auto valueWidget = new ValueWidget(parent);
237  valueWidget->setTextFormat(Qt::PlainText);
238  valueWidget->setWordWrap(true);
239  valueWidget->setAlignment(Qt::AlignTop | Qt::AlignLeft);
240  connect(valueWidget, &ValueWidget::linkActivated, this, &WidgetFactory::slotLinkActivated);
241 
242  return valueWidget;
243 }
244 
245 //
246 // Data Synchronization
247 //
248 
249 void WidgetFactory::slotCommentChanged(const QString &comment)
250 {
251  for (const KFileItem &item : std::as_const(m_items)) {
252  QUrl url = item.targetUrl();
253  if (!url.isLocalFile()) {
254  continue;
255  }
256  KFileMetaData::UserMetaData md(url.toLocalFile());
257  md.setUserComment(comment);
258  }
259  Q_EMIT dataChangeStarted();
260  Q_EMIT dataChangeFinished();
261 }
262 
263 void WidgetFactory::slotRatingChanged(int rating)
264 {
265  for (const KFileItem &item : std::as_const(m_items)) {
266  QUrl url = item.targetUrl();
267  if (!url.isLocalFile()) {
268  continue;
269  }
270  KFileMetaData::UserMetaData md(url.toLocalFile());
271  md.setRating(rating);
272  }
273  Q_EMIT dataChangeStarted();
274  Q_EMIT dataChangeFinished();
275 }
276 
277 void WidgetFactory::slotTagsChanged(const QStringList &tags)
278 {
279  if (m_tagWidget) {
280  for (const KFileItem &item : std::as_const(m_items)) {
281  QUrl url = item.targetUrl();
282  if (!url.isLocalFile()) {
283  continue;
284  }
285  KFileMetaData::UserMetaData md(url.toLocalFile());
286 
287  // When multiple tags are selected one doesn't want to loose the old tags
288  // of any of the resources. Unless specifically removed.
289  QStringList newTags = md.tags() + tags;
290  newTags.removeDuplicates();
291 
292  for (const QString &tag : std::as_const(m_prevTags)) {
293  if (!tags.contains(tag)) {
294  newTags.removeAll(tag);
295  }
296  }
297  md.setTags(newTags);
298  }
299 
300  m_prevTags = tags;
301  Q_EMIT dataChangeStarted();
302  Q_EMIT dataChangeFinished();
303  }
304 }
305 
306 //
307 // Notifications
308 //
309 
310 void WidgetFactory::slotLinkActivated(const QString &url)
311 {
312  Q_EMIT urlActivated(QUrl::fromUserInput(url));
313 }
314 
315 void WidgetFactory::slotTagClicked(const QString &tag)
316 {
317  QUrl url;
318  url.setScheme(QStringLiteral("tags"));
319  url.setPath(tag);
320 
321  Q_EMIT urlActivated(url);
322 }
323 
324 //
325 // Accessor Methods
326 //
327 void WidgetFactory::setReadOnly(bool value)
328 {
329  m_readOnly = value;
330 }
331 
332 void WidgetFactory::setItems(const KFileItemList &items)
333 {
334  m_items = items;
335 }
336 
337 Baloo::DateFormats WidgetFactory::dateFormat() const
338 {
339  return static_cast<Baloo::DateFormats>(m_dateFormat);
340 }
341 
342 void Baloo::WidgetFactory::setDateFormat(const Baloo::DateFormats format)
343 {
344  m_dateFormat = static_cast<QLocale::FormatType>(format);
345 }
RichText
typedef Alignment
void setText(const QString &)
QUrl toUrl() const const
int removeAll(const T &value)
T value() const const
void setTextFormat(Qt::TextFormat)
TextSingleLine
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QTime time() const const
void setFont(const QFont &)
void setForegroundRole(QPalette::ColorRole role)
QString formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const
QUrl geoUri(const QVariant &location)
void selectionChanged(const QStringList &tags)
Emitted whenever the selection of tags changes.
void setTextInteractionFlags(Qt::TextInteractionFlags flags)
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
void setWordWrap(bool on)
void setScheme(const QString &scheme)
KSERVICE_EXPORT KService::Ptr preferredService(const QString &mimeType)
TextBrowserInteraction
double toDouble(bool *ok) const const
KCOREADDONS_EXPORT QString csqueeze(const QString &str, int maxlen=40)
QVariant::Type type() const const
void setAlignment(Qt::Alignment)
char * toString(const T &value)
void setNumericMode(bool on)
QString formatRelativeDate(const QDate &date, QLocale::FormatType format) const
QString toString(qlonglong i) const const
int toInt(bool *ok) const const
QString toLocalFile() const const
void ratingChanged(int rating)
QDateTime fromString(const QString &string, Qt::DateFormat format)
static PropertyInfo fromName(const QString &name)
QString join(const QString &separator) const const
int hour() const const
int removeDuplicates()
void setToolTip(const QString &)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
int second() const const
void setObjectName(const QString &name)
void setPath(const QString &path, QUrl::ParsingMode mode)
bool isLocalFile() const const
QDate date() const const
QList< QVariant > toList() const const
LeftToRight
bool isValid() const const
void tagClicked(const QString &)
This signal is emitted whenever a tag is clicked.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QList::iterator begin()
QStringList toStringList() const const
QPalette::ColorRole foregroundRole() const const
QList::iterator end()
Allows to change a selection of tags.
Definition: tagwidget.h:30
int minute() const const
QUrl fromUserInput(const QString &userInput)
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 19 2022 04:12:56 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.