File indexing completed on 2024-04-14 04:35:08

0001 /*
0002     SPDX-FileCopyrightText: 2012-2013 Vishesh Handa <me@vhanda.in>
0003 
0004     Adapted from KFileMetadataWidget
0005     SPDX-FileCopyrightText: 2008 Sebastian Trueg <trueg@kde.org>
0006     SPDX-FileCopyrightText: 2009-2010 Peter Penz <peter.penz@gmx.at>
0007 
0008     SPDX-License-Identifier: LGPL-2.1-or-later
0009 */
0010 
0011 #include "filemetadatawidget.h"
0012 #include "filemetadataprovider.h"
0013 #include "metadatafilter.h"
0014 #include "widgetfactory.h"
0015 
0016 #include <KConfig>
0017 #include <KConfigGroup>
0018 
0019 #include <QCheckBox>
0020 #include <QGridLayout>
0021 #include <QLabel>
0022 #include <QList>
0023 #include <QSpacerItem>
0024 #include <QString>
0025 
0026 using namespace Baloo;
0027 
0028 class Baloo::FileMetaDataWidgetPrivate
0029 {
0030 public:
0031     struct Row {
0032         QCheckBox *checkBox;
0033         QLabel *label;
0034         QWidget *value;
0035     };
0036 
0037     explicit FileMetaDataWidgetPrivate(FileMetaDataWidget *parent);
0038     ~FileMetaDataWidgetPrivate();
0039 
0040     FileMetaDataWidgetPrivate(const FileMetaDataWidgetPrivate&) = delete;
0041     FileMetaDataWidget& operator=(const FileMetaDataWidgetPrivate&) = delete;
0042 
0043     void deleteRows();
0044 
0045     void slotLoadingFinished();
0046 
0047     QStringList sortedKeys(const QVariantMap &data) const;
0048     QLabel *createLabel(const QString &key, const QString &itemLabel, FileMetaDataWidget *parent);
0049 
0050     void saveConfig();
0051 
0052     QList<Row> m_rows;
0053     FileMetaDataProvider *m_provider = nullptr;
0054     QGridLayout *m_gridLayout = nullptr;
0055 
0056     MetadataFilter *m_filter = nullptr;
0057     WidgetFactory *m_widgetFactory = nullptr;
0058 
0059     QMap<QString, bool> m_visibilityChanged;
0060     bool m_configureVisibleProperties = false;
0061 
0062 private:
0063     FileMetaDataWidget *const q;
0064 };
0065 
0066 FileMetaDataWidgetPrivate::FileMetaDataWidgetPrivate(FileMetaDataWidget *parent)
0067     : m_rows()
0068     , q(parent)
0069 {
0070     m_filter = new MetadataFilter(q);
0071 
0072     m_widgetFactory = new WidgetFactory(q);
0073     QObject::connect(m_widgetFactory, &WidgetFactory::urlActivated, q, &FileMetaDataWidget::urlActivated);
0074 
0075     // TODO: If KFileMetaDataProvider might get a public class in future KDE releases,
0076     // the following code should be moved into KFileMetaDataWidget::setModel():
0077     m_provider = new FileMetaDataProvider(q);
0078     QObject::connect(m_provider, &FileMetaDataProvider::loadingFinished, q, [this]() {
0079         slotLoadingFinished();
0080     });
0081 }
0082 
0083 FileMetaDataWidgetPrivate::~FileMetaDataWidgetPrivate() = default;
0084 
0085 void FileMetaDataWidgetPrivate::deleteRows()
0086 {
0087     for (const Row &row : std::as_const(m_rows)) {
0088         delete row.label;
0089         row.value->deleteLater();
0090         if (row.checkBox) {
0091             row.checkBox->deleteLater();
0092         }
0093     }
0094 
0095     m_rows.clear();
0096 }
0097 
0098 QLabel *FileMetaDataWidgetPrivate::createLabel(const QString &key, const QString &itemLabel, FileMetaDataWidget *parent)
0099 {
0100     auto label = new QLabel(itemLabel + QLatin1Char(':'), parent);
0101     label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
0102     label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0103     label->setForegroundRole(parent->foregroundRole());
0104     label->setFont(parent->font());
0105     label->setWordWrap(true);
0106     label->setAlignment(Qt::AlignTop | Qt::AlignRight);
0107     label->setObjectName(QStringLiteral("L_%1").arg(key));
0108     return label;
0109 }
0110 
0111 void FileMetaDataWidgetPrivate::slotLoadingFinished()
0112 {
0113     deleteRows();
0114 
0115     if (m_gridLayout == nullptr) {
0116         m_gridLayout = new QGridLayout(q);
0117         m_gridLayout->setContentsMargins(0, 0, 0, 0);
0118         m_gridLayout->setSpacing(q->fontMetrics().height() / 4);
0119     }
0120 
0121     QVariantMap data = m_provider->data();
0122     QStringList active;
0123     if (m_configureVisibleProperties) {
0124         active = m_filter->filter(data).keys();
0125         auto changedIt = m_visibilityChanged.constBegin();
0126         while (changedIt != m_visibilityChanged.constEnd()) {
0127             if (changedIt.value()) {
0128                 active.append(changedIt.key());
0129             } else {
0130                 active.removeAll(changedIt.key());
0131             }
0132             changedIt++;
0133         }
0134         m_widgetFactory->setReadOnly(true);
0135         m_gridLayout->setColumnStretch(0, 1);
0136         m_gridLayout->setColumnStretch(1, 3);
0137         m_gridLayout->setColumnStretch(2, 0);
0138         m_gridLayout->setColumnStretch(3, 6);
0139     } else {
0140         data = m_filter->filter(data);
0141         m_widgetFactory->setReadOnly(m_provider->isReadOnly());
0142         m_gridLayout->setColumnStretch(0, 4);
0143         m_gridLayout->setColumnStretch(1, 0);
0144         m_gridLayout->setColumnStretch(2, 6);
0145         m_gridLayout->setColumnStretch(3, 0);
0146     }
0147 
0148     int rowIndex = 0;
0149     // Iterate through all remaining items.
0150     // Embed the label and the value as new row in the widget
0151     const QStringList keys = sortedKeys(data);
0152     const int spacerWidth = QFontMetrics(q->font()).size(Qt::TextSingleLine, QStringLiteral(" ")).width();
0153 
0154     const int labelColumn = m_configureVisibleProperties ? 1 : 0;
0155 
0156     for (const auto &key : keys) {
0157         Row row;
0158         if (m_configureVisibleProperties) {
0159             row.checkBox = new QCheckBox(q);
0160             if (active.contains(key)) {
0161                 row.checkBox->setChecked(true);
0162             }
0163             m_gridLayout->addWidget(row.checkBox, rowIndex, 0, Qt::AlignTop | Qt::AlignRight);
0164             QObject::connect(row.checkBox, &QCheckBox::stateChanged, q, [this, key](int state) {
0165                 this->m_visibilityChanged[key] = (state == Qt::Checked);
0166             });
0167         } else {
0168             row.checkBox = nullptr;
0169         }
0170 
0171         row.label = createLabel(key, m_provider->label(key), q);
0172         m_gridLayout->addWidget(row.label, rowIndex, labelColumn + 0, Qt::AlignRight);
0173 
0174         m_gridLayout->addItem(new QSpacerItem(spacerWidth, 1), rowIndex, labelColumn + 1);
0175 
0176         row.value = m_widgetFactory->createWidget(key, data[key], q);
0177         m_gridLayout->addWidget(row.value, rowIndex, labelColumn + 2, Qt::AlignLeft);
0178 
0179         m_gridLayout->setRowStretch(rowIndex, 0);
0180 
0181         // Remember the label and value-widget as row
0182         m_rows.append(row);
0183         ++rowIndex;
0184     }
0185 
0186     // Add vertical stretch - when the widget is embedded with extra vertical
0187     // space, it should be added at the bottom, not distributed between the
0188     // items.
0189     m_gridLayout->addItem(new QSpacerItem(0, 0), rowIndex, 0, 1, -1);
0190     m_gridLayout->setRowStretch(rowIndex, 1);
0191 
0192     q->updateGeometry();
0193     Q_EMIT q->metaDataRequestFinished(m_provider->items());
0194 }
0195 
0196 QStringList FileMetaDataWidgetPrivate::sortedKeys(const QVariantMap &data) const
0197 {
0198     // Create a map, where the translated label prefixed with the
0199     // sort priority acts as key. The data of each entry is the URI
0200     // of the data. By this the all URIs are sorted by the sort priority
0201     // and sub sorted by the translated labels.
0202     QMap<QString, QString> map;
0203     QVariantMap::const_iterator hashIt = data.constBegin();
0204     while (hashIt != data.constEnd()) {
0205         const QString propName = hashIt.key();
0206 
0207         QString key = m_provider->group(propName);
0208         key += m_provider->label(propName);
0209 
0210         map.insert(key, propName);
0211         ++hashIt;
0212     }
0213 
0214     // Apply the URIs from the map to the list that will get returned.
0215     // The list will then be alphabetically ordered by the translated labels of the URIs.
0216     QStringList list;
0217     QMap<QString, QString>::const_iterator mapIt = map.constBegin();
0218     while (mapIt != map.constEnd()) {
0219         list.append(mapIt.value());
0220         ++mapIt;
0221     }
0222 
0223     return list;
0224 }
0225 
0226 void FileMetaDataWidgetPrivate::saveConfig()
0227 {
0228     if (m_visibilityChanged.isEmpty()) {
0229         return;
0230     }
0231 
0232     KConfig config(QStringLiteral("baloofileinformationrc"), KConfig::NoGlobals);
0233     KConfigGroup showGroup = config.group(QStringLiteral("Show"));
0234 
0235     auto changedIt = m_visibilityChanged.constBegin();
0236     while (changedIt != m_visibilityChanged.constEnd()) {
0237         showGroup.writeEntry(changedIt.key(), changedIt.value());
0238         changedIt++;
0239     }
0240 
0241     showGroup.sync();
0242 }
0243 
0244 FileMetaDataWidget::FileMetaDataWidget(QWidget *parent)
0245     : QWidget(parent)
0246     , d(new FileMetaDataWidgetPrivate(this))
0247 {
0248 }
0249 
0250 FileMetaDataWidget::~FileMetaDataWidget() = default;
0251 
0252 void FileMetaDataWidget::setItems(const KFileItemList &items)
0253 {
0254     d->m_provider->setItems(items);
0255     d->m_widgetFactory->setItems(items);
0256 }
0257 
0258 KFileItemList FileMetaDataWidget::items() const
0259 {
0260     return d->m_provider->items();
0261 }
0262 
0263 void FileMetaDataWidget::setReadOnly(bool readOnly)
0264 {
0265     d->m_provider->setReadOnly(readOnly);
0266     d->m_widgetFactory->setReadOnly(readOnly);
0267 }
0268 
0269 bool FileMetaDataWidget::isReadOnly() const
0270 {
0271     return d->m_provider->isReadOnly();
0272 }
0273 void FileMetaDataWidget::setDateFormat(const DateFormats format)
0274 {
0275     d->m_widgetFactory->setDateFormat(format);
0276 }
0277 
0278 DateFormats FileMetaDataWidget::dateFormat() const
0279 {
0280     return d->m_widgetFactory->dateFormat();
0281 }
0282 
0283 QSize FileMetaDataWidget::sizeHint() const
0284 {
0285     if (d->m_gridLayout == nullptr) {
0286         return QWidget::sizeHint();
0287     }
0288 
0289     // Calculate the required width for the labels and values
0290     int leftWidthMax = 0;
0291     int rightWidthMax = 0;
0292     int rightWidthAverage = 0;
0293     for (const FileMetaDataWidgetPrivate::Row &row : std::as_const(d->m_rows)) {
0294         const QWidget *valueWidget = row.value;
0295         const int rightWidth = valueWidget->sizeHint().width();
0296         rightWidthAverage += rightWidth;
0297         if (rightWidth > rightWidthMax) {
0298             rightWidthMax = rightWidth;
0299         }
0300 
0301         const int leftWidth = row.label->sizeHint().width();
0302         if (leftWidth > leftWidthMax) {
0303             leftWidthMax = leftWidth;
0304         }
0305     }
0306 
0307     // Some value widgets might return a very huge width for the size hint.
0308     // Limit the maximum width to the double width of the overall average
0309     // to assure a less messed layout.
0310     if (d->m_rows.count() > 1) {
0311         rightWidthAverage /= d->m_rows.count();
0312         if (rightWidthMax > rightWidthAverage * 2) {
0313             rightWidthMax = rightWidthAverage * 2;
0314         }
0315     }
0316 
0317     // Based on the available width calculate the required height
0318     const auto margins = d->m_gridLayout->contentsMargins();
0319     int height = margins.top() + margins.bottom() + d->m_gridLayout->spacing() * (d->m_rows.count() - 1);
0320     for (const FileMetaDataWidgetPrivate::Row &row : std::as_const(d->m_rows)) {
0321         const QWidget *valueWidget = row.value;
0322         const int rowHeight = qMax(row.label->heightForWidth(leftWidthMax), valueWidget->heightForWidth(rightWidthMax));
0323         height += rowHeight;
0324     }
0325 
0326     const int width = margins.left() + margins.right() + leftWidthMax + d->m_gridLayout->spacing() + rightWidthMax;
0327 
0328     return QSize{width, height};
0329 }
0330 
0331 void FileMetaDataWidget::setConfigurationMode(ConfigurationMode mode)
0332 {
0333     if (mode == ConfigurationMode::ReStart) {
0334         d->m_configureVisibleProperties = true;
0335     } else if (mode == ConfigurationMode::Accept) {
0336         d->saveConfig();
0337         d->m_configureVisibleProperties = false;
0338     } else if (mode == ConfigurationMode::Cancel) {
0339         d->m_configureVisibleProperties = false;
0340     }
0341     d->m_visibilityChanged.clear();
0342     d->slotLoadingFinished();
0343 }
0344 
0345 #include "moc_filemetadatawidget.cpp"