File indexing completed on 2024-05-05 04:19:17

0001 /*
0002 Gwenview: an image viewer
0003 Copyright 2007 Aurélien Gâteau <agateau@kde.org>
0004 
0005 This program is free software; you can redistribute it and/or
0006 modify it under the terms of the GNU General Public License
0007 as published by the Free Software Foundation; either version 2
0008 of the License, or (at your option) any later version.
0009 
0010 This program is distributed in the hope that it will be useful,
0011 but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 GNU General Public License for more details.
0014 
0015 You should have received a copy of the GNU General Public License
0016 along with this program; if not, write to the Free Software
0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0018 
0019 */
0020 #include "infocontextmanageritem.h"
0021 
0022 // Qt
0023 #include <QLabel>
0024 #include <QPointer>
0025 #include <QPushButton>
0026 #include <QScrollArea>
0027 #include <QVBoxLayout>
0028 
0029 // KF
0030 #include <KFileItem>
0031 #include <KLocalizedString>
0032 
0033 // Local
0034 #include "imagemetainfodialog.h"
0035 #include "sidebar.h"
0036 #include <lib/archiveutils.h>
0037 #include <lib/contextmanager.h>
0038 #include <lib/document/document.h>
0039 #include <lib/document/documentfactory.h>
0040 #include <lib/eventwatcher.h>
0041 #include <lib/gvdebug.h>
0042 #include <lib/gwenviewconfig.h>
0043 #include <lib/preferredimagemetainfomodel.h>
0044 
0045 namespace Gwenview
0046 {
0047 #undef ENABLE_LOG
0048 #undef LOG
0049 // #define ENABLE_LOG
0050 #ifdef ENABLE_LOG
0051 #define LOG(x) qCDebug(GWENVIEW_APP_LOG) << x
0052 #else
0053 #define LOG(x) ;
0054 #endif
0055 
0056 /**
0057  * This widget is capable of showing multiple lines of key/value pairs.
0058  */
0059 class KeyValueWidget : public QWidget
0060 {
0061     struct Row {
0062         Row(QWidget *parent)
0063             : keyLabel(new QLabel(parent))
0064             , valueLabel(new QLabel(parent))
0065         {
0066             initLabel(keyLabel);
0067             initLabel(valueLabel);
0068 
0069             QPalette pal = keyLabel->palette();
0070             QColor color = pal.color(QPalette::WindowText);
0071             color.setAlphaF(0.65);
0072             pal.setColor(QPalette::WindowText, color);
0073             keyLabel->setPalette(pal);
0074 
0075             valueLabel->setIndent(parent->fontInfo().pixelSize());
0076         }
0077 
0078         ~Row()
0079         {
0080             delete keyLabel;
0081             delete valueLabel;
0082         }
0083 
0084         int setLabelGeometries(int rowY, int labelWidth)
0085         {
0086             int labelHeight = keyLabel->heightForWidth(labelWidth);
0087             keyLabel->setGeometry(0, rowY, labelWidth, labelHeight);
0088             rowY += labelHeight;
0089             labelHeight = valueLabel->heightForWidth(labelWidth);
0090             valueLabel->setGeometry(0, rowY, labelWidth, labelHeight);
0091             rowY += labelHeight;
0092             return rowY;
0093         }
0094 
0095         int heightForWidth(int width) const
0096         {
0097             return keyLabel->heightForWidth(width) + valueLabel->heightForWidth(width);
0098         }
0099 
0100         static void initLabel(QLabel *label)
0101         {
0102             label->setWordWrap(true);
0103             label->show();
0104             label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
0105         }
0106 
0107         QLabel *const keyLabel;
0108         QLabel *const valueLabel;
0109     };
0110 
0111 public:
0112     explicit KeyValueWidget(QWidget *parent = nullptr)
0113         : QWidget(parent)
0114     {
0115         QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Fixed);
0116         policy.setHeightForWidth(true);
0117         setSizePolicy(policy);
0118     }
0119     ~KeyValueWidget() override
0120     {
0121         qDeleteAll(mRows);
0122         mRows.clear();
0123     }
0124 
0125     QSize sizeHint() const override
0126     {
0127         int width = 150;
0128         int height = heightForWidth(width);
0129         return QSize(width, height);
0130     }
0131 
0132     int heightForWidth(int w) const override
0133     {
0134         int height = 0;
0135         for (Row *row : qAsConst(mRows)) {
0136             height += row->heightForWidth(w);
0137         }
0138         return height;
0139     }
0140 
0141     void clear()
0142     {
0143         qDeleteAll(mRows);
0144         mRows.clear();
0145         updateGeometry();
0146     }
0147 
0148     void addRow(const QString &key, const QString &value)
0149     {
0150         Row *row = new Row(this);
0151         row->keyLabel->setText(i18nc("@item:intext %1 is a key, we append a colon to it. A value is displayed after", "%1:", key));
0152         row->valueLabel->setText(value);
0153         mRows << row;
0154     }
0155 
0156     static bool rowsLessThan(const Row *row1, const Row *row2)
0157     {
0158         return row1->keyLabel->text() < row2->keyLabel->text();
0159     }
0160 
0161     void finishAddRows()
0162     {
0163         std::sort(mRows.begin(), mRows.end(), KeyValueWidget::rowsLessThan);
0164         updateGeometry();
0165     }
0166 
0167     void layoutRows()
0168     {
0169         // Layout labels manually: I tried to use a QVBoxLayout but for some
0170         // reason when using software animations the widget blinks when going
0171         // from one image to another
0172         int rowY = 0;
0173         const int labelWidth = width();
0174         for (Row *row : qAsConst(mRows)) {
0175             rowY = row->setLabelGeometries(rowY, labelWidth);
0176         }
0177     }
0178 
0179 protected:
0180     void showEvent(QShowEvent *event) override
0181     {
0182         QWidget::showEvent(event);
0183         layoutRows();
0184     }
0185 
0186     void resizeEvent(QResizeEvent *event) override
0187     {
0188         QWidget::resizeEvent(event);
0189         layoutRows();
0190     }
0191 
0192 private:
0193     QVector<Row *> mRows;
0194 };
0195 
0196 struct InfoContextManagerItemPrivate {
0197     InfoContextManagerItem *q;
0198     SideBarGroup *mGroup;
0199 
0200     // One selection fields
0201     QScrollArea *mOneFileWidget;
0202     KeyValueWidget *mKeyValueWidget;
0203     Document::Ptr mDocument;
0204 
0205     // Multiple selection fields
0206     QLabel *mMultipleFilesLabel;
0207 
0208     QPointer<ImageMetaInfoDialog> mImageMetaInfoDialog;
0209 
0210     void updateMetaInfoDialog()
0211     {
0212         if (!mImageMetaInfoDialog) {
0213             return;
0214         }
0215         ImageMetaInfoModel *model = mDocument ? mDocument->metaInfo() : nullptr;
0216         mImageMetaInfoDialog->setMetaInfo(model, GwenviewConfig::preferredMetaInfoKeyList());
0217     }
0218 
0219     void setupGroup()
0220     {
0221         mOneFileWidget = new QScrollArea();
0222         mOneFileWidget->setFrameStyle(QFrame::NoFrame);
0223         mOneFileWidget->setWidgetResizable(true);
0224 
0225         mKeyValueWidget = new KeyValueWidget;
0226 
0227         auto moreLabel = new QLabel(mOneFileWidget);
0228         moreLabel->setText(QStringLiteral("<a href='#'>%1</a>").arg(i18nc("@action show more image meta info", "Show more details...")));
0229         // for some reason, this label appears much further down the page without the following line
0230         moreLabel->setAlignment(Qt::AlignLeft);
0231 
0232         auto content = new QWidget;
0233         auto layout = new QVBoxLayout(content);
0234         layout->setContentsMargins(0, 0, 0, 0);
0235         layout->addWidget(mKeyValueWidget);
0236         layout->addWidget(moreLabel);
0237 
0238         mOneFileWidget->setWidget(content);
0239 
0240         mMultipleFilesLabel = new QLabel();
0241 
0242         mGroup = new SideBarGroup(i18nc("@title:group", "Image Information"));
0243         q->setWidget(mGroup);
0244         mGroup->addWidget(mOneFileWidget);
0245         mGroup->addWidget(mMultipleFilesLabel);
0246 
0247         EventWatcher::install(mGroup, QEvent::Show, q, SLOT(updateSideBarContent()));
0248 
0249         QObject::connect(moreLabel, &QLabel::linkActivated, q, &InfoContextManagerItem::showMetaInfoDialog);
0250     }
0251 
0252     void forgetCurrentDocument()
0253     {
0254         if (mDocument) {
0255             QObject::disconnect(mDocument.data(), nullptr, q, nullptr);
0256             // "Garbage collect" document
0257             mDocument = nullptr;
0258         }
0259     }
0260 };
0261 
0262 InfoContextManagerItem::InfoContextManagerItem(ContextManager *manager)
0263     : AbstractContextManagerItem(manager)
0264     , d(new InfoContextManagerItemPrivate)
0265 {
0266     d->q = this;
0267     d->setupGroup();
0268     connect(contextManager(), &ContextManager::selectionChanged, this, &InfoContextManagerItem::updateSideBarContent);
0269     connect(contextManager(), &ContextManager::selectionDataChanged, this, &InfoContextManagerItem::updateSideBarContent);
0270 }
0271 
0272 InfoContextManagerItem::~InfoContextManagerItem()
0273 {
0274     delete d;
0275 }
0276 
0277 void InfoContextManagerItem::updateSideBarContent()
0278 {
0279     LOG("updateSideBarContent");
0280     if (!d->mGroup->isVisible()) {
0281         LOG("updateSideBarContent: not visible, not updating");
0282         return;
0283     }
0284     LOG("updateSideBarContent: really updating");
0285 
0286     KFileItemList itemList = contextManager()->selectedFileItemList();
0287     if (itemList.isEmpty()) {
0288         d->forgetCurrentDocument();
0289         d->mOneFileWidget->hide();
0290         d->mMultipleFilesLabel->hide();
0291         d->updateMetaInfoDialog();
0292         return;
0293     }
0294 
0295     KFileItem item = itemList.first();
0296     if (itemList.count() == 1 && !ArchiveUtils::fileItemIsDirOrArchive(item)) {
0297         fillOneFileGroup(item);
0298     } else {
0299         fillMultipleItemsGroup(itemList);
0300     }
0301     d->updateMetaInfoDialog();
0302 }
0303 
0304 void InfoContextManagerItem::fillOneFileGroup(const KFileItem &item)
0305 {
0306     d->mOneFileWidget->show();
0307     d->mMultipleFilesLabel->hide();
0308 
0309     d->forgetCurrentDocument();
0310     d->mDocument = DocumentFactory::instance()->load(item.url());
0311     connect(d->mDocument.data(), &Document::metaInfoUpdated, this, &InfoContextManagerItem::updateOneFileInfo);
0312 
0313     d->updateMetaInfoDialog();
0314     updateOneFileInfo();
0315 }
0316 
0317 void InfoContextManagerItem::fillMultipleItemsGroup(const KFileItemList &itemList)
0318 {
0319     d->forgetCurrentDocument();
0320 
0321     int folderCount = 0, fileCount = 0;
0322     for (const KFileItem &item : itemList) {
0323         if (item.isDir()) {
0324             folderCount++;
0325         } else {
0326             fileCount++;
0327         }
0328     }
0329 
0330     if (folderCount == 0) {
0331         d->mMultipleFilesLabel->setText(i18ncp("@label", "%1 file selected", "%1 files selected", fileCount));
0332     } else if (fileCount == 0) {
0333         d->mMultipleFilesLabel->setText(i18ncp("@label", "%1 folder selected", "%1 folders selected", folderCount));
0334     } else {
0335         d->mMultipleFilesLabel->setText(i18nc("@label. The two parameters are strings like '2 folders' and '1 file'.",
0336                                               "%1 and %2 selected",
0337                                               i18np("%1 folder", "%1 folders", folderCount),
0338                                               i18np("%1 file", "%1 files", fileCount)));
0339     }
0340     d->mOneFileWidget->hide();
0341     d->mMultipleFilesLabel->show();
0342 }
0343 
0344 void InfoContextManagerItem::updateOneFileInfo()
0345 {
0346     if (!d->mDocument) {
0347         return;
0348     }
0349 
0350     ImageMetaInfoModel *metaInfoModel = d->mDocument->metaInfo();
0351     d->mKeyValueWidget->clear();
0352     const QStringList preferredMetaInfoKeyList = GwenviewConfig::preferredMetaInfoKeyList();
0353     for (const QString &key : preferredMetaInfoKeyList) {
0354         QString label;
0355         QString value;
0356         metaInfoModel->getInfoForKey(key, &label, &value);
0357         if (!label.isEmpty() && !value.isEmpty()) {
0358             d->mKeyValueWidget->addRow(label, value);
0359         }
0360     }
0361     d->mKeyValueWidget->finishAddRows();
0362     d->mKeyValueWidget->layoutRows();
0363 }
0364 
0365 void InfoContextManagerItem::showMetaInfoDialog()
0366 {
0367     if (!d->mImageMetaInfoDialog) {
0368         d->mImageMetaInfoDialog = new ImageMetaInfoDialog(d->mOneFileWidget);
0369         d->mImageMetaInfoDialog->setAttribute(Qt::WA_DeleteOnClose, true);
0370         connect(d->mImageMetaInfoDialog.data(),
0371                 &ImageMetaInfoDialog::preferredMetaInfoKeyListChanged,
0372                 this,
0373                 &InfoContextManagerItem::slotPreferredMetaInfoKeyListChanged);
0374     }
0375     d->mImageMetaInfoDialog->setMetaInfo(d->mDocument ? d->mDocument->metaInfo() : nullptr, GwenviewConfig::preferredMetaInfoKeyList());
0376     d->mImageMetaInfoDialog->show();
0377 }
0378 
0379 void InfoContextManagerItem::slotPreferredMetaInfoKeyListChanged(const QStringList &list)
0380 {
0381     GwenviewConfig::setPreferredMetaInfoKeyList(list);
0382     GwenviewConfig::self()->save();
0383     updateOneFileInfo();
0384 }
0385 
0386 } // namespace
0387 
0388 #include "moc_infocontextmanageritem.cpp"