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"