File indexing completed on 2024-05-12 05:47:41

0001 /*
0002  * SPDX-FileCopyrightText: 2006-2009 Peter Penz <peter.penz19@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "informationpanel.h"
0008 
0009 #include "informationpanelcontent.h"
0010 
0011 #include <KDirNotify>
0012 #include <KIO/StatJob>
0013 #include <KJobWidgets>
0014 #include <KLocalizedString>
0015 
0016 #include <Baloo/FileMetaDataWidget>
0017 
0018 #include <QApplication>
0019 #include <QMenu>
0020 #include <QShowEvent>
0021 #include <QTimer>
0022 #include <QVBoxLayout>
0023 
0024 #include "dolphin_informationpanelsettings.h"
0025 
0026 InformationPanel::InformationPanel(QWidget *parent)
0027     : Panel(parent)
0028     , m_initialized(false)
0029     , m_infoTimer(nullptr)
0030     , m_urlChangedTimer(nullptr)
0031     , m_resetUrlTimer(nullptr)
0032     , m_shownUrl()
0033     , m_urlCandidate()
0034     , m_invalidUrlCandidate()
0035     , m_hoveredItem()
0036     , m_selection()
0037     , m_folderStatJob(nullptr)
0038     , m_content(nullptr)
0039 {
0040 }
0041 
0042 InformationPanel::~InformationPanel()
0043 {
0044 }
0045 
0046 void InformationPanel::setSelection(const KFileItemList &selection)
0047 {
0048     m_selection = selection;
0049 
0050     if (!isVisible()) {
0051         return;
0052     }
0053 
0054     const int count = selection.count();
0055     if (count == 0) {
0056         if (!isEqualToShownUrl(url())) {
0057             m_shownUrl = url();
0058             showItemInfo();
0059         }
0060         m_infoTimer->stop();
0061     } else {
0062         if ((count == 1) && !selection.first().url().isEmpty()) {
0063             m_urlCandidate = selection.first().url();
0064         }
0065         showItemInfo();
0066     }
0067 }
0068 
0069 void InformationPanel::requestDelayedItemInfo(const KFileItem &item)
0070 {
0071     if (!isVisible() || !InformationPanelSettings::showHovered()) {
0072         return;
0073     }
0074 
0075     if (QApplication::mouseButtons() & Qt::LeftButton) {
0076         // Ignore the request of an item information when a rubberband
0077         // selection is ongoing.
0078         return;
0079     }
0080 
0081     if (item.isNull()) {
0082         m_hoveredItem = KFileItem();
0083         return;
0084     }
0085 
0086     cancelRequest();
0087 
0088     m_hoveredItem = item;
0089     m_infoTimer->start();
0090 }
0091 
0092 bool InformationPanel::urlChanged()
0093 {
0094     if (!url().isValid()) {
0095         return false;
0096     }
0097 
0098     if (!isVisible()) {
0099         return true;
0100     }
0101 
0102     cancelRequest();
0103     m_selection.clear();
0104 
0105     if (!isEqualToShownUrl(url())) {
0106         m_shownUrl = url();
0107 
0108         // Update the content with a delay. This gives
0109         // the directory lister the chance to show the content
0110         // before expensive operations are done to show
0111         // meta information.
0112         m_urlChangedTimer->start();
0113     }
0114 
0115     return true;
0116 }
0117 
0118 void InformationPanel::showEvent(QShowEvent *event)
0119 {
0120     Panel::showEvent(event);
0121     if (!event->spontaneous()) {
0122         if (!m_initialized) {
0123             // do a delayed initialization so that no performance
0124             // penalty is given when Dolphin is started with a closed
0125             // Information Panel
0126             init();
0127         }
0128 
0129         m_shownUrl = url();
0130         showItemInfo();
0131     }
0132 }
0133 
0134 void InformationPanel::resizeEvent(QResizeEvent *event)
0135 {
0136     if (isVisible()) {
0137         m_urlCandidate = m_shownUrl;
0138         m_infoTimer->start();
0139     }
0140     Panel::resizeEvent(event);
0141 }
0142 
0143 void InformationPanel::contextMenuEvent(QContextMenuEvent *event)
0144 {
0145     showContextMenu(event->globalPos());
0146     Panel::contextMenuEvent(event);
0147 }
0148 
0149 void InformationPanel::showContextMenu(const QPoint &pos)
0150 {
0151     QMenu popup(this);
0152 
0153     QAction *previewAction = popup.addAction(i18nc("@action:inmenu", "Preview"));
0154     previewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-preview")));
0155     previewAction->setCheckable(true);
0156     previewAction->setChecked(InformationPanelSettings::previewsShown());
0157 
0158     QAction *previewAutoPlayAction = popup.addAction(i18nc("@action:inmenu", "Auto-Play media files"));
0159     previewAutoPlayAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
0160     previewAutoPlayAction->setCheckable(true);
0161     previewAutoPlayAction->setChecked(InformationPanelSettings::previewsAutoPlay());
0162 
0163     QAction *showHoveredAction = popup.addAction(i18nc("@action:inmenu", "Show item on hover"));
0164     showHoveredAction->setIcon(QIcon::fromTheme(QStringLiteral("followmouse")));
0165     showHoveredAction->setCheckable(true);
0166     showHoveredAction->setChecked(InformationPanelSettings::showHovered());
0167 
0168     QAction *configureAction = popup.addAction(i18nc("@action:inmenu", "Configureā€¦"));
0169     configureAction->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0170     if (m_inConfigurationMode) {
0171         configureAction->setEnabled(false);
0172     }
0173 
0174     QAction *dateformatAction = popup.addAction(i18nc("@action:inmenu", "Condensed Date"));
0175     dateformatAction->setIcon(QIcon::fromTheme(QStringLiteral("change-date-symbolic")));
0176     dateformatAction->setCheckable(true);
0177     dateformatAction->setChecked(InformationPanelSettings::dateFormat() == static_cast<int>(Baloo::DateFormats::ShortFormat));
0178 
0179     popup.addSeparator();
0180     const auto actions = customContextMenuActions();
0181     for (QAction *action : actions) {
0182         popup.addAction(action);
0183     }
0184 
0185     // Open the popup and adjust the settings for the
0186     // selected action.
0187     QAction *action = popup.exec(pos);
0188     if (!action) {
0189         return;
0190     }
0191 
0192     const bool isChecked = action->isChecked();
0193     if (action == previewAction) {
0194         InformationPanelSettings::setPreviewsShown(isChecked);
0195         m_content->refreshPreview();
0196     } else if (action == previewAutoPlayAction) {
0197         InformationPanelSettings::setPreviewsAutoPlay(isChecked);
0198         m_content->setPreviewAutoPlay(isChecked);
0199     } else if (action == showHoveredAction) {
0200         InformationPanelSettings::setShowHovered(isChecked);
0201         if (!isChecked) {
0202             m_hoveredItem = KFileItem();
0203             showItemInfo();
0204         }
0205     } else if (action == configureAction) {
0206         m_inConfigurationMode = true;
0207         m_content->configureShownProperties();
0208     } else if (action == dateformatAction) {
0209         int dateFormat = static_cast<int>(isChecked ? Baloo::DateFormats::ShortFormat : Baloo::DateFormats::LongFormat);
0210 
0211         InformationPanelSettings::setDateFormat(dateFormat);
0212         m_content->refreshMetaData();
0213     }
0214 }
0215 
0216 void InformationPanel::showItemInfo()
0217 {
0218     if (!isVisible()) {
0219         return;
0220     }
0221 
0222     cancelRequest();
0223     //qDebug() << "showItemInfo" << m_fileItem;
0224 
0225     if (m_hoveredItem.isNull() && (m_selection.count() > 1)) {
0226         // The information for a selection of items should be shown
0227         m_content->showItems(m_selection);
0228     } else {
0229         // The information for exactly one item should be shown
0230         KFileItem item;
0231         if (!m_hoveredItem.isNull() && InformationPanelSettings::showHovered()) {
0232             item = m_hoveredItem;
0233         } else if (!m_selection.isEmpty()) {
0234             Q_ASSERT(m_selection.count() == 1);
0235             item = m_selection.first();
0236         }
0237 
0238         if (!item.isNull()) {
0239             m_shownUrl = item.url();
0240             m_content->showItem(item);
0241             return;
0242         }
0243 
0244         // No item is hovered and no selection has been done: provide
0245         // an item for the currently shown directory.
0246         m_shownUrl = url();
0247         m_folderStatJob = KIO::stat(m_shownUrl, KIO::StatJob::SourceSide, KIO::StatDefaultDetails | KIO::StatRecursiveSize, KIO::HideProgressInfo);
0248         if (m_folderStatJob->uiDelegate()) {
0249             KJobWidgets::setWindow(m_folderStatJob, this);
0250         }
0251         connect(m_folderStatJob, &KIO::Job::result, this, &InformationPanel::slotFolderStatFinished);
0252     }
0253 }
0254 
0255 void InformationPanel::slotFolderStatFinished(KJob *job)
0256 {
0257     m_folderStatJob = nullptr;
0258     const KIO::UDSEntry entry = static_cast<KIO::StatJob *>(job)->statResult();
0259     m_content->showItem(KFileItem(entry, m_shownUrl));
0260 }
0261 
0262 void InformationPanel::slotInfoTimeout()
0263 {
0264     m_shownUrl = m_urlCandidate;
0265     m_urlCandidate.clear();
0266     showItemInfo();
0267 }
0268 
0269 void InformationPanel::reset()
0270 {
0271     if (m_invalidUrlCandidate == m_shownUrl) {
0272         m_invalidUrlCandidate = QUrl();
0273 
0274         // The current URL is still invalid. Reset
0275         // the content to show the directory URL.
0276         m_selection.clear();
0277         m_shownUrl = url();
0278         showItemInfo();
0279     }
0280 }
0281 
0282 void InformationPanel::slotFileRenamed(const QString &source, const QString &dest)
0283 {
0284     auto sourceUrl = QUrl::fromUserInput(source);
0285     if (m_shownUrl == sourceUrl) {
0286         auto destUrl = QUrl::fromUserInput(dest);
0287 
0288         if ((m_selection.count() == 1) && (m_selection[0].url() == sourceUrl)) {
0289             m_selection[0] = KFileItem(destUrl);
0290             // Implementation note: Updating the selection is only required if exactly one
0291             // item is selected, as the name of the item is shown. If this should change
0292             // in future: Before parsing the whole selection take care to test possible
0293             // performance bottlenecks when renaming several hundreds of files.
0294         }
0295 
0296         showItemInfo();
0297     }
0298 }
0299 
0300 void InformationPanel::slotFilesAdded(const QString &directory)
0301 {
0302     if (m_shownUrl == QUrl::fromUserInput(directory)) {
0303         // If the 'trash' icon changes because the trash has been emptied or got filled,
0304         // the signal filesAdded("trash:/") will be emitted.
0305         requestDelayedItemInfo(KFileItem());
0306     }
0307 }
0308 
0309 void InformationPanel::slotFilesItemChanged(const KFileItemList &changedFileItems)
0310 {
0311     const auto item = changedFileItems.findByUrl(m_shownUrl);
0312     if (!item.isNull()) {
0313         showItemInfo();
0314     }
0315 }
0316 
0317 void InformationPanel::slotFilesChanged(const QStringList &files)
0318 {
0319     for (const QString &fileName : files) {
0320         if (m_shownUrl == QUrl::fromUserInput(fileName)) {
0321             showItemInfo();
0322             break;
0323         }
0324     }
0325 }
0326 
0327 void InformationPanel::slotFilesRemoved(const QStringList &files)
0328 {
0329     for (const QString &fileName : files) {
0330         if (m_shownUrl == QUrl::fromUserInput(fileName)) {
0331             // the currently shown item has been removed, show
0332             // the parent directory as fallback
0333             markUrlAsInvalid();
0334             break;
0335         }
0336     }
0337 }
0338 
0339 void InformationPanel::slotEnteredDirectory(const QString &directory)
0340 {
0341     Q_UNUSED(directory)
0342 }
0343 
0344 void InformationPanel::slotLeftDirectory(const QString &directory)
0345 {
0346     if (m_shownUrl == QUrl::fromUserInput(directory)) {
0347         // The signal 'leftDirectory' is also emitted when a media
0348         // has been unmounted. In this case no directory change will be
0349         // done in Dolphin, but the Information Panel must be updated to
0350         // indicate an invalid directory.
0351         markUrlAsInvalid();
0352     }
0353 }
0354 
0355 void InformationPanel::cancelRequest()
0356 {
0357     delete m_folderStatJob;
0358     m_folderStatJob = nullptr;
0359 
0360     m_infoTimer->stop();
0361     m_resetUrlTimer->stop();
0362     // Don't reset m_urlChangedTimer. As it is assured that the timeout of m_urlChangedTimer
0363     // has the smallest interval (see init()), it is not possible that the exceeded timer
0364     // would overwrite an information provided by a selection or hovering.
0365 
0366     m_invalidUrlCandidate.clear();
0367     m_urlCandidate.clear();
0368 }
0369 
0370 bool InformationPanel::isEqualToShownUrl(const QUrl &url) const
0371 {
0372     return m_shownUrl.matches(url, QUrl::StripTrailingSlash);
0373 }
0374 
0375 void InformationPanel::markUrlAsInvalid()
0376 {
0377     m_invalidUrlCandidate = m_shownUrl;
0378     m_resetUrlTimer->start();
0379 }
0380 
0381 void InformationPanel::init()
0382 {
0383     m_infoTimer = new QTimer(this);
0384     m_infoTimer->setInterval(300);
0385     m_infoTimer->setSingleShot(true);
0386     connect(m_infoTimer, &QTimer::timeout, this, &InformationPanel::slotInfoTimeout);
0387 
0388     m_urlChangedTimer = new QTimer(this);
0389     m_urlChangedTimer->setInterval(200);
0390     m_urlChangedTimer->setSingleShot(true);
0391     connect(m_urlChangedTimer, &QTimer::timeout, this, &InformationPanel::showItemInfo);
0392 
0393     m_resetUrlTimer = new QTimer(this);
0394     m_resetUrlTimer->setInterval(1000);
0395     m_resetUrlTimer->setSingleShot(true);
0396     connect(m_resetUrlTimer, &QTimer::timeout, this, &InformationPanel::reset);
0397 
0398     Q_ASSERT(m_urlChangedTimer->interval() < m_infoTimer->interval());
0399     Q_ASSERT(m_urlChangedTimer->interval() < m_resetUrlTimer->interval());
0400 
0401     org::kde::KDirNotify *dirNotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this);
0402     connect(dirNotify, &OrgKdeKDirNotifyInterface::FileRenamed, this, &InformationPanel::slotFileRenamed);
0403     connect(dirNotify, &OrgKdeKDirNotifyInterface::FilesAdded, this, &InformationPanel::slotFilesAdded);
0404     connect(dirNotify, &OrgKdeKDirNotifyInterface::FilesChanged, this, &InformationPanel::slotFilesChanged);
0405     connect(dirNotify, &OrgKdeKDirNotifyInterface::FilesRemoved, this, &InformationPanel::slotFilesRemoved);
0406     connect(dirNotify, &OrgKdeKDirNotifyInterface::enteredDirectory, this, &InformationPanel::slotEnteredDirectory);
0407     connect(dirNotify, &OrgKdeKDirNotifyInterface::leftDirectory, this, &InformationPanel::slotLeftDirectory);
0408 
0409     m_content = new InformationPanelContent(this);
0410     connect(m_content, &InformationPanelContent::urlActivated, this, &InformationPanel::urlActivated);
0411     connect(m_content, &InformationPanelContent::configurationFinished, this, [this]() {
0412         m_inConfigurationMode = false;
0413     });
0414     connect(m_content, &InformationPanelContent::contextMenuRequested, this, &InformationPanel::showContextMenu);
0415 
0416     QVBoxLayout *layout = new QVBoxLayout(this);
0417     layout->setContentsMargins(0, 0, 0, 0);
0418     layout->addWidget(m_content);
0419 
0420     m_initialized = true;
0421 }
0422 
0423 #include "moc_informationpanel.cpp"