Baloo Widgets

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

KDE's Doxygen guidelines are available online.