Baloo Widgets

filemetadatawidget.cpp
1 /*
2  SPDX-FileCopyrightText: 2012-2013 Vishesh Handa <[email protected]>
3 
4  Adapted from KFileMetadataWidget
5  SPDX-FileCopyrightText: 2008 Sebastian Trueg <[email protected]>
6  SPDX-FileCopyrightText: 2009-2010 Peter Penz <[email protected]>
7 
8  SPDX-License-Identifier: LGPL-2.1-or-later
9 */
10 
11 #include "filemetadatawidget.h"
12 #include "filemetadataprovider.h"
13 #include "metadatafilter.h"
14 #include "widgetfactory.h"
15 
16 #include <KConfig>
17 #include <KConfigGroup>
18 
19 #include <KFileMetaData/UserMetaData>
20 
21 #include <QCheckBox>
22 #include <QDebug>
23 #include <QFileInfo>
24 #include <QGridLayout>
25 #include <QLabel>
26 #include <QList>
27 #include <QSet>
28 #include <QSpacerItem>
29 #include <QString>
30 #include <QTimer>
31 
32 using namespace Baloo;
33 
34 class Baloo::FileMetaDataWidgetPrivate
35 {
36 public:
37  struct Row {
38  QCheckBox *checkBox;
39  QLabel *label;
40  QWidget *value;
41  };
42 
43  FileMetaDataWidgetPrivate(FileMetaDataWidget *parent);
44  ~FileMetaDataWidgetPrivate();
45 
46  void deleteRows();
47 
48  void slotLoadingFinished();
49  void slotLinkActivated(const QString &link);
50  void slotDataChangeStarted();
51  void slotDataChangeFinished();
52 
53  QStringList sortedKeys(const QVariantMap &data) const;
54  QLabel *createLabel(const QString &key, const QString &itemLabel, FileMetaDataWidget *parent);
55 
56  void saveConfig();
57 
58  QList<Row> m_rows;
59  FileMetaDataProvider *m_provider = nullptr;
60  QGridLayout *m_gridLayout = nullptr;
61 
62  MetadataFilter *m_filter = nullptr;
63  WidgetFactory *m_widgetFactory = nullptr;
64 
65  QMap<QString, bool> m_visibilityChanged;
66  bool m_configureVisibleProperties = false;
67 
68 private:
69  FileMetaDataWidget *const q;
70 };
71 
72 FileMetaDataWidgetPrivate::FileMetaDataWidgetPrivate(FileMetaDataWidget *parent)
73  : m_rows()
74  , q(parent)
75 {
76  m_filter = new MetadataFilter(q);
77 
78  m_widgetFactory = new WidgetFactory(q);
79  QObject::connect(m_widgetFactory, &WidgetFactory::urlActivated, q, &FileMetaDataWidget::urlActivated);
80 
81  // TODO: If KFileMetaDataProvider might get a public class in future KDE releases,
82  // the following code should be moved into KFileMetaDataWidget::setModel():
83  m_provider = new FileMetaDataProvider(q);
85  slotLoadingFinished();
86  });
87 }
88 
89 FileMetaDataWidgetPrivate::~FileMetaDataWidgetPrivate() = default;
90 
91 void FileMetaDataWidgetPrivate::deleteRows()
92 {
93  for (const Row &row : std::as_const(m_rows)) {
94  delete row.label;
95  row.value->deleteLater();
96  if (row.checkBox) {
97  row.checkBox->deleteLater();
98  }
99  }
100 
101  m_rows.clear();
102 }
103 
104 QLabel *FileMetaDataWidgetPrivate::createLabel(const QString &key, const QString &itemLabel, FileMetaDataWidget *parent)
105 {
106  auto label = new QLabel(itemLabel + QLatin1Char(':'), parent);
109  label->setForegroundRole(parent->foregroundRole());
110  label->setFont(parent->font());
111  label->setWordWrap(true);
112  label->setAlignment(Qt::AlignTop | Qt::AlignRight);
113  label->setObjectName(QStringLiteral("L_%1").arg(key));
114  return label;
115 }
116 
117 void FileMetaDataWidgetPrivate::slotLoadingFinished()
118 {
119  deleteRows();
120 
121  if (m_gridLayout == nullptr) {
122  m_gridLayout = new QGridLayout(q);
123  m_gridLayout->setContentsMargins(0, 0, 0, 0);
124  m_gridLayout->setSpacing(q->fontMetrics().height() / 4);
125  }
126 
127  QVariantMap data = m_provider->data();
128  QStringList active;
129  if (m_configureVisibleProperties) {
130  active = m_filter->filter(data).keys();
131  auto changedIt = m_visibilityChanged.constBegin();
132  while (changedIt != m_visibilityChanged.constEnd()) {
133  if (changedIt.value()) {
134  active.append(changedIt.key());
135  } else {
136  active.removeAll(changedIt.key());
137  }
138  changedIt++;
139  }
140  m_widgetFactory->setReadOnly(true);
141  m_gridLayout->setColumnStretch(0, 1);
142  m_gridLayout->setColumnStretch(1, 3);
143  m_gridLayout->setColumnStretch(2, 0);
144  m_gridLayout->setColumnStretch(3, 6);
145  } else {
146  data = m_filter->filter(data);
147  m_widgetFactory->setReadOnly(m_provider->isReadOnly());
148  m_gridLayout->setColumnStretch(0, 4);
149  m_gridLayout->setColumnStretch(1, 0);
150  m_gridLayout->setColumnStretch(2, 6);
151  m_gridLayout->setColumnStretch(3, 0);
152  }
153 
154  int rowIndex = 0;
155  // Iterate through all remaining items.
156  // Embed the label and the value as new row in the widget
157  const QStringList keys = sortedKeys(data);
158  const int spacerWidth = QFontMetrics(q->font()).size(Qt::TextSingleLine, QStringLiteral(" ")).width();
159 
160  const int labelColumn = m_configureVisibleProperties ? 1 : 0;
161 
162  for (const auto &key : keys) {
163  Row row;
164  if (m_configureVisibleProperties) {
165  row.checkBox = new QCheckBox(q);
166  if (active.contains(key)) {
167  row.checkBox->setChecked(true);
168  }
169  m_gridLayout->addWidget(row.checkBox, rowIndex, 0, Qt::AlignTop | Qt::AlignRight);
170  QObject::connect(row.checkBox, &QCheckBox::stateChanged, q, [this, key](int state) {
171  this->m_visibilityChanged[key] = (state == Qt::Checked);
172  });
173  } else {
174  row.checkBox = nullptr;
175  }
176 
177  row.label = createLabel(key, m_provider->label(key), q);
178  m_gridLayout->addWidget(row.label, rowIndex, labelColumn + 0, Qt::AlignRight);
179 
180  m_gridLayout->addItem(new QSpacerItem(spacerWidth, 1), rowIndex, labelColumn + 1);
181 
182  row.value = m_widgetFactory->createWidget(key, data[key], q);
183  m_gridLayout->addWidget(row.value, rowIndex, labelColumn + 2, Qt::AlignLeft);
184 
185  m_gridLayout->setRowStretch(rowIndex, 0);
186 
187  // Remember the label and value-widget as row
188  m_rows.append(row);
189  ++rowIndex;
190  }
191 
192  // Add vertical stretch - when the widget is embedded with extra vertical
193  // space, it should be added at the bottom, not distributed between the
194  // items.
195  m_gridLayout->addItem(new QSpacerItem(0, 0), rowIndex, 0, 1, -1);
196  m_gridLayout->setRowStretch(rowIndex, 1);
197 
198  q->updateGeometry();
199  Q_EMIT q->metaDataRequestFinished(m_provider->items());
200 }
201 
202 void FileMetaDataWidgetPrivate::slotLinkActivated(const QString &link)
203 {
204  const QUrl url = QUrl::fromUserInput(link);
205  if (url.isValid()) {
206  Q_EMIT q->urlActivated(url);
207  }
208 }
209 
210 void FileMetaDataWidgetPrivate::slotDataChangeStarted()
211 {
212  q->setEnabled(false);
213 }
214 
215 void FileMetaDataWidgetPrivate::slotDataChangeFinished()
216 {
217  q->setEnabled(true);
218 }
219 
220 QStringList FileMetaDataWidgetPrivate::sortedKeys(const QVariantMap &data) const
221 {
222  // Create a map, where the translated label prefixed with the
223  // sort priority acts as key. The data of each entry is the URI
224  // of the data. By this the all URIs are sorted by the sort priority
225  // and sub sorted by the translated labels.
227  QVariantMap::const_iterator hashIt = data.constBegin();
228  while (hashIt != data.constEnd()) {
229  const QString propName = hashIt.key();
230 
231  QString key = m_provider->group(propName);
232  key += m_provider->label(propName);
233 
234  map.insert(key, propName);
235  ++hashIt;
236  }
237 
238  // Apply the URIs from the map to the list that will get returned.
239  // The list will then be alphabetically ordered by the translated labels of the URIs.
241  QMap<QString, QString>::const_iterator mapIt = map.constBegin();
242  while (mapIt != map.constEnd()) {
243  list.append(mapIt.value());
244  ++mapIt;
245  }
246 
247  return list;
248 }
249 
250 void FileMetaDataWidgetPrivate::saveConfig()
251 {
252  if (m_visibilityChanged.isEmpty()) {
253  return;
254  }
255 
256  KConfig config(QStringLiteral("baloofileinformationrc"), KConfig::NoGlobals);
257  KConfigGroup showGroup = config.group("Show");
258 
259  auto changedIt = m_visibilityChanged.constBegin();
260  while (changedIt != m_visibilityChanged.constEnd()) {
261  showGroup.writeEntry(changedIt.key(), changedIt.value());
262  changedIt++;
263  }
264 
265  showGroup.sync();
266 }
267 
268 FileMetaDataWidget::FileMetaDataWidget(QWidget *parent)
269  : QWidget(parent)
270  , d(new FileMetaDataWidgetPrivate(this))
271 {
272 }
273 
274 FileMetaDataWidget::~FileMetaDataWidget() = default;
275 
276 void FileMetaDataWidget::setItems(const KFileItemList &items)
277 {
278  d->m_provider->setItems(items);
279  d->m_widgetFactory->setItems(items);
280 }
281 
282 KFileItemList FileMetaDataWidget::items() const
283 {
284  return d->m_provider->items();
285 }
286 
287 void FileMetaDataWidget::setReadOnly(bool readOnly)
288 {
289  d->m_provider->setReadOnly(readOnly);
290  d->m_widgetFactory->setReadOnly(readOnly);
291 }
292 
293 bool FileMetaDataWidget::isReadOnly() const
294 {
295  return d->m_provider->isReadOnly();
296 }
297 void FileMetaDataWidget::setDateFormat(const DateFormats format)
298 {
299  d->m_widgetFactory->setDateFormat(format);
300 }
301 
302 DateFormats FileMetaDataWidget::dateFormat() const
303 {
304  return d->m_widgetFactory->dateFormat();
305 }
306 
307 QSize FileMetaDataWidget::sizeHint() const
308 {
309  if (d->m_gridLayout == nullptr) {
310  return QWidget::sizeHint();
311  }
312 
313  // Calculate the required width for the labels and values
314  int leftWidthMax = 0;
315  int rightWidthMax = 0;
316  int rightWidthAverage = 0;
317  for (const FileMetaDataWidgetPrivate::Row &row : std::as_const(d->m_rows)) {
318  const QWidget *valueWidget = row.value;
319  const int rightWidth = valueWidget->sizeHint().width();
320  rightWidthAverage += rightWidth;
321  if (rightWidth > rightWidthMax) {
322  rightWidthMax = rightWidth;
323  }
324 
325  const int leftWidth = row.label->sizeHint().width();
326  if (leftWidth > leftWidthMax) {
327  leftWidthMax = leftWidth;
328  }
329  }
330 
331  // Some value widgets might return a very huge width for the size hint.
332  // Limit the maximum width to the double width of the overall average
333  // to assure a less messed layout.
334  if (d->m_rows.count() > 1) {
335  rightWidthAverage /= d->m_rows.count();
336  if (rightWidthMax > rightWidthAverage * 2) {
337  rightWidthMax = rightWidthAverage * 2;
338  }
339  }
340 
341  // Based on the available width calculate the required height
342  int height = getMargin() * 2 + d->m_gridLayout->spacing() * (d->m_rows.count() - 1);
343  for (const FileMetaDataWidgetPrivate::Row &row : std::as_const(d->m_rows)) {
344  const QWidget *valueWidget = row.value;
345  const int rowHeight = qMax(row.label->heightForWidth(leftWidthMax), valueWidget->heightForWidth(rightWidthMax));
346  height += rowHeight;
347  }
348 
349  const int width = getMargin() * 2 + leftWidthMax + d->m_gridLayout->spacing() + rightWidthMax;
350 
351  return QSize{width, height};
352 }
353 
354 void FileMetaDataWidget::setConfigurationMode(ConfigurationMode mode)
355 {
356  if (mode == ConfigurationMode::ReStart) {
357  d->m_configureVisibleProperties = true;
358  } else if (mode == ConfigurationMode::Accept) {
359  d->saveConfig();
360  d->m_configureVisibleProperties = false;
361  } else if (mode == ConfigurationMode::Cancel) {
362  d->m_configureVisibleProperties = false;
363  }
364  d->m_visibilityChanged.clear();
365  d->slotLoadingFinished();
366 }
367 
368 int FileMetaDataWidget::getMargin() const
369 {
370  int left, top, right, bottom;
371  d->m_gridLayout->getContentsMargins(&left, &top, &right, &bottom);
372  if (left == top && top == right && right == bottom) {
373  return left;
374  } else {
375  return -1;
376  }
377 }
378 
379 #include "moc_filemetadatawidget.cpp"
void append(const T &value)
void loadingFinished()
Emitted once per KFileMetaDataProvider::setItems() after data loading is finished.
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
AlignTop
QTextStream & right(QTextStream &stream)
int removeAll(const T &value)
const T value(const Key &key, const T &defaultValue) const const
QTextStream & left(QTextStream &stream)
TextSingleLine
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
QList::const_iterator constBegin() const const
int width() const const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
void stateChanged(int state)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool isValid() const const
TextSelectableByMouse
QStringList filter(QStringView str, Qt::CaseSensitivity cs) const const
KSharedConfigPtr config()
QSize size(int flags, const QString &text, int tabStops, int *tabArray) const const
QString label(StandardShortcut id)
virtual int heightForWidth(int w) const const
bool sync() override
Provides the data for the MetaDataWidget.
QFuture< void > map(Sequence &sequence, MapFunctor function)
QUrl fromUserInput(const QString &userInput)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Aug 11 2022 04:13:42 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.