diff --git a/CMakeLists.txt b/CMakeLists.txt index f009aa837..8d13dfedd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,155 +1,155 @@ cmake_minimum_required(VERSION 3.0) # KDE Application Version, managed by release script set (KDE_APPLICATIONS_VERSION_MAJOR "20") set (KDE_APPLICATIONS_VERSION_MINOR "03") set (KDE_APPLICATIONS_VERSION_MICRO "70") set (KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") project(Dolphin VERSION ${KDE_APPLICATIONS_VERSION}) set(QT_MIN_VERSION "5.11.0") -set(KF5_MIN_VERSION "5.63.0") +set(KF5_MIN_VERSION "5.64.0") # ECM setup find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) include(GenerateExportHeader) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMQtDeclareLoggingCategory) ecm_setup_version(${KDE_APPLICATIONS_VERSION} VARIABLE_PREFIX DOLPHIN VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/src/dolphin_version.h" ) ecm_setup_version("5.0.0" VARIABLE_PREFIX DOLPHINVCS VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/dolphinvcs_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/DolphinVcsConfigVersion.cmake" SOVERSION 5 ) ecm_setup_version("5.0.0" VARIABLE_PREFIX DOLPHINPRIVATE SOVERSION 5 ) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Concurrent Widgets Gui DBus ) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS DocTools Init KCMUtils NewStuff CoreAddons I18n DBusAddons Bookmarks Config KIO Parts Solid IconThemes Completion TextWidgets Notifications Crash WindowSystem ) find_package(KF5 ${KF5_MIN_VERSION} OPTIONAL_COMPONENTS Activities ) set_package_properties(KF5Activities PROPERTIES DESCRIPTION "KActivities libraries" URL "http://www.kde.org" TYPE OPTIONAL PURPOSE "For tracking which folders are frequently accessed on a Plasma desktop" ) find_package(Phonon4Qt5 CONFIG REQUIRED) find_package(KF5Baloo ${KF5_MIN_VERSION}) set_package_properties(KF5Baloo PROPERTIES DESCRIPTION "Baloo Core libraries" URL "http://www.kde.org" TYPE OPTIONAL PURPOSE "For adding desktop-wide search and tagging support to dolphin" ) find_package(KF5BalooWidgets 19.07.70) set_package_properties(KF5BalooWidgets PROPERTIES DESCRIPTION "Baloos Widgets" URL "http://www.kde.org" TYPE OPTIONAL ) find_package(KF5FileMetaData ${KF5_MIN_VERSION}) set_package_properties(KF5FileMetaData PROPERTIES URL "https://projects.kde.org/kfilemetadata" TYPE OPTIONAL PURPOSE "For accessing file metadata labels" ) if (KF5Activities_FOUND) set(HAVE_KACTIVITIES TRUE) endif() if (KF5Baloo_FOUND AND KF5BalooWidgets_FOUND AND KF5FileMetaData_FOUND) message(STATUS "Baloo packages are found") set(HAVE_BALOO TRUE) else() message(WARNING "Baloo packages not found. They are needed for the metadata features of Dolphin (including the information panel).") endif() # TODO: drop HAVE_TERMINAL once we are sure the terminal panel works on Windows too. if(WIN32) set(HAVE_TERMINAL FALSE) else() set(HAVE_TERMINAL TRUE) endif() add_subdirectory(src) add_subdirectory(doc) # CMake files set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/DolphinVcs") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/DolphinVcsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/DolphinVcsConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/DolphinVcsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/DolphinVcsConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT DolphinVcsTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE DolphinVcsTargets.cmake ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dolphinvcs_version.h" DESTINATION "${KDE_INSTALL_INCLUDEDIR}/Dolphin" COMPONENT Devel ) configure_file(org.kde.dolphin.FileManager1.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.dolphin.FileManager1.service) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.dolphin.FileManager1.service DESTINATION ${DBUS_SERVICES_INSTALL_DIR}) install(FILES dolphin.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/panels/information/informationpanel.cpp b/src/panels/information/informationpanel.cpp index e6c3bf32a..23e7f1922 100644 --- a/src/panels/information/informationpanel.cpp +++ b/src/panels/information/informationpanel.cpp @@ -1,428 +1,428 @@ /*************************************************************************** * Copyright (C) 2006-2009 by Peter Penz * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "informationpanel.h" #include "informationpanelcontent.h" #include #include #include #include #include #include #include #include #include #include #include #include "dolphin_informationpanelsettings.h" InformationPanel::InformationPanel(QWidget* parent) : Panel(parent), m_initialized(false), m_infoTimer(nullptr), m_urlChangedTimer(nullptr), m_resetUrlTimer(nullptr), m_shownUrl(), m_urlCandidate(), m_invalidUrlCandidate(), m_fileItem(), m_selection(), m_folderStatJob(nullptr), m_content(nullptr) { } InformationPanel::~InformationPanel() { } void InformationPanel::setSelection(const KFileItemList& selection) { m_selection = selection; m_fileItem = KFileItem(); if (!isVisible()) { return; } const int count = selection.count(); if (count == 0) { if (!isEqualToShownUrl(url())) { m_shownUrl = url(); showItemInfo(); } } else { if ((count == 1) && !selection.first().url().isEmpty()) { m_urlCandidate = selection.first().url(); } m_infoTimer->start(); } } void InformationPanel::requestDelayedItemInfo(const KFileItem& item) { if (!isVisible() || (item.isNull() && m_fileItem.isNull())) { return; } if (QApplication::mouseButtons() & Qt::LeftButton) { // Ignore the request of an item information when a rubberband // selection is ongoing. return; } cancelRequest(); if (item.isNull()) { // The cursor is above the viewport. If files are selected, // show information regarding the selection. if (!m_selection.isEmpty()) { m_fileItem = KFileItem(); m_infoTimer->start(); } } else if (item.url().isValid() && !isEqualToShownUrl(item.url())) { // The cursor is above an item that is not shown currently m_urlCandidate = item.url(); m_fileItem = item; m_infoTimer->start(); } } bool InformationPanel::urlChanged() { if (!url().isValid()) { return false; } if (!isVisible()) { return true; } cancelRequest(); m_selection.clear(); if (!isEqualToShownUrl(url())) { m_shownUrl = url(); m_fileItem = KFileItem(); // Update the content with a delay. This gives // the directory lister the chance to show the content // before expensive operations are done to show // meta information. m_urlChangedTimer->start(); } return true; } void InformationPanel::showEvent(QShowEvent* event) { Panel::showEvent(event); if (!event->spontaneous()) { if (!m_initialized) { // do a delayed initialization so that no performance // penalty is given when Dolphin is started with a closed // Information Panel init(); } m_shownUrl = url(); showItemInfo(); } } void InformationPanel::resizeEvent(QResizeEvent* event) { if (isVisible()) { m_urlCandidate = m_shownUrl; m_infoTimer->start(); } Panel::resizeEvent(event); } void InformationPanel::contextMenuEvent(QContextMenuEvent* event) { showContextMenu(event->globalPos()); Panel::contextMenuEvent(event); } void InformationPanel::showContextMenu(const QPoint &pos) { QMenu popup(this); QAction* previewAction = popup.addAction(i18nc("@action:inmenu", "Preview")); previewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-preview"))); previewAction->setCheckable(true); previewAction->setChecked(InformationPanelSettings::previewsShown()); QAction* previewAutoPlayAction = popup.addAction(i18nc("@action:inmenu", "Auto-Play media files")); previewAutoPlayAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); previewAutoPlayAction->setCheckable(true); previewAutoPlayAction->setChecked(InformationPanelSettings::previewsAutoPlay()); QAction* configureAction = popup.addAction(i18nc("@action:inmenu", "Configure...")); configureAction->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); if (m_inConfigurationMode) { configureAction->setEnabled(false); } QAction* dateformatAction = popup.addAction(i18nc("@action:inmenu", "Condensed Date")); dateformatAction->setIcon(QIcon::fromTheme(QStringLiteral("change-date-symbolic"))); dateformatAction->setCheckable(true); dateformatAction->setChecked(InformationPanelSettings::dateFormat() == static_cast(Baloo::DateFormats::ShortFormat)); popup.addSeparator(); const auto actions = customContextMenuActions(); for (QAction *action : actions) { popup.addAction(action); } // Open the popup and adjust the settings for the // selected action. QAction* action = popup.exec(pos); if (!action) { return; } const bool isChecked = action->isChecked(); if (action == previewAction) { InformationPanelSettings::setPreviewsShown(isChecked); m_content->refreshPreview(); } else if (action == configureAction) { m_inConfigurationMode = true; m_content->configureShownProperties(); } if (action == dateformatAction) { int dateFormat = static_cast(isChecked ? Baloo::DateFormats::ShortFormat : Baloo::DateFormats::LongFormat); InformationPanelSettings::setDateFormat(dateFormat); m_content->refreshMetaData(); } else if (action == previewAutoPlayAction) { InformationPanelSettings::setPreviewsAutoPlay(isChecked); m_content->setPreviewAutoPlay(isChecked); } } void InformationPanel::showItemInfo() { if (!isVisible()) { return; } cancelRequest(); if (m_fileItem.isNull() && (m_selection.count() > 1)) { // The information for a selection of items should be shown m_content->showItems(m_selection); } else { // The information for exactly one item should be shown KFileItem item; if (!m_fileItem.isNull()) { item = m_fileItem; } else if (!m_selection.isEmpty()) { Q_ASSERT(m_selection.count() == 1); item = m_selection.first(); } if (item.isNull()) { // No item is hovered and no selection has been done: provide // an item for the currently shown directory. m_folderStatJob = KIO::stat(url(), KIO::HideProgressInfo); if (m_folderStatJob->uiDelegate()) { KJobWidgets::setWindow(m_folderStatJob, this); } connect(m_folderStatJob, &KIO::Job::result, this, &InformationPanel::slotFolderStatFinished); } else { m_content->showItem(item); } } } void InformationPanel::slotFolderStatFinished(KJob* job) { m_folderStatJob = nullptr; const KIO::UDSEntry entry = static_cast(job)->statResult(); m_content->showItem(KFileItem(entry, m_shownUrl)); } void InformationPanel::slotInfoTimeout() { m_shownUrl = m_urlCandidate; m_urlCandidate.clear(); showItemInfo(); } void InformationPanel::reset() { if (m_invalidUrlCandidate == m_shownUrl) { m_invalidUrlCandidate = QUrl(); // The current URL is still invalid. Reset // the content to show the directory URL. m_selection.clear(); m_shownUrl = url(); m_fileItem = KFileItem(); showItemInfo(); } } void InformationPanel::slotFileRenamed(const QString& source, const QString& dest) { - if (m_shownUrl == QUrl::fromLocalFile(source)) { - m_shownUrl = QUrl::fromLocalFile(dest); + if (m_shownUrl == QUrl::fromUserInput(source)) { + m_shownUrl = QUrl::fromUserInput(dest); m_fileItem = KFileItem(m_shownUrl); if ((m_selection.count() == 1) && (m_selection[0].url() == QUrl::fromLocalFile(source))) { m_selection[0] = m_fileItem; // Implementation note: Updating the selection is only required if exactly one // item is selected, as the name of the item is shown. If this should change // in future: Before parsing the whole selection take care to test possible // performance bottlenecks when renaming several hundreds of files. } showItemInfo(); } } void InformationPanel::slotFilesAdded(const QString& directory) { - if (m_shownUrl == QUrl::fromLocalFile(directory)) { + if (m_shownUrl == QUrl::fromUserInput(directory)) { // If the 'trash' icon changes because the trash has been emptied or got filled, // the signal filesAdded("trash:/") will be emitted. - KFileItem item(QUrl::fromLocalFile(directory)); + KFileItem item(QUrl::fromUserInput(directory)); requestDelayedItemInfo(item); } } void InformationPanel::slotFilesChanged(const QStringList& files) { for (const QString& fileName : files) { - if (m_shownUrl == QUrl::fromLocalFile(fileName)) { + if (m_shownUrl == QUrl::fromUserInput(fileName)) { showItemInfo(); break; } } } void InformationPanel::slotFilesRemoved(const QStringList& files) { for (const QString& fileName : files) { - if (m_shownUrl == QUrl::fromLocalFile(fileName)) { + if (m_shownUrl == QUrl::fromUserInput(fileName)) { // the currently shown item has been removed, show // the parent directory as fallback markUrlAsInvalid(); break; } } } void InformationPanel::slotEnteredDirectory(const QString& directory) { - if (m_shownUrl == QUrl::fromLocalFile(directory)) { - KFileItem item(QUrl::fromLocalFile(directory)); + if (m_shownUrl == QUrl::fromUserInput(directory)) { + KFileItem item(QUrl::fromUserInput(directory)); requestDelayedItemInfo(item); } } void InformationPanel::slotLeftDirectory(const QString& directory) { - if (m_shownUrl == QUrl::fromLocalFile(directory)) { + if (m_shownUrl == QUrl::fromUserInput(directory)) { // The signal 'leftDirectory' is also emitted when a media // has been unmounted. In this case no directory change will be // done in Dolphin, but the Information Panel must be updated to // indicate an invalid directory. markUrlAsInvalid(); } } void InformationPanel::cancelRequest() { delete m_folderStatJob; m_folderStatJob = nullptr; m_infoTimer->stop(); m_resetUrlTimer->stop(); // Don't reset m_urlChangedTimer. As it is assured that the timeout of m_urlChangedTimer // has the smallest interval (see init()), it is not possible that the exceeded timer // would overwrite an information provided by a selection or hovering. m_invalidUrlCandidate.clear(); m_urlCandidate.clear(); } bool InformationPanel::isEqualToShownUrl(const QUrl& url) const { return m_shownUrl.matches(url, QUrl::StripTrailingSlash); } void InformationPanel::markUrlAsInvalid() { m_invalidUrlCandidate = m_shownUrl; m_resetUrlTimer->start(); } void InformationPanel::init() { m_infoTimer = new QTimer(this); m_infoTimer->setInterval(300); m_infoTimer->setSingleShot(true); connect(m_infoTimer, &QTimer::timeout, this, &InformationPanel::slotInfoTimeout); m_urlChangedTimer = new QTimer(this); m_urlChangedTimer->setInterval(200); m_urlChangedTimer->setSingleShot(true); connect(m_urlChangedTimer, &QTimer::timeout, this, &InformationPanel::showItemInfo); m_resetUrlTimer = new QTimer(this); m_resetUrlTimer->setInterval(1000); m_resetUrlTimer->setSingleShot(true); connect(m_resetUrlTimer, &QTimer::timeout, this, &InformationPanel::reset); Q_ASSERT(m_urlChangedTimer->interval() < m_infoTimer->interval()); Q_ASSERT(m_urlChangedTimer->interval() < m_resetUrlTimer->interval()); org::kde::KDirNotify* dirNotify = new org::kde::KDirNotify(QString(), QString(), QDBusConnection::sessionBus(), this); connect(dirNotify, &OrgKdeKDirNotifyInterface::FileRenamed, this, &InformationPanel::slotFileRenamed); connect(dirNotify, &OrgKdeKDirNotifyInterface::FilesAdded, this, &InformationPanel::slotFilesAdded); connect(dirNotify, &OrgKdeKDirNotifyInterface::FilesChanged, this, &InformationPanel::slotFilesChanged); connect(dirNotify, &OrgKdeKDirNotifyInterface::FilesRemoved, this, &InformationPanel::slotFilesRemoved); connect(dirNotify, &OrgKdeKDirNotifyInterface::enteredDirectory, this, &InformationPanel::slotEnteredDirectory); connect(dirNotify, &OrgKdeKDirNotifyInterface::leftDirectory, this, &InformationPanel::slotLeftDirectory); m_content = new InformationPanelContent(this); connect(m_content, &InformationPanelContent::urlActivated, this, &InformationPanel::urlActivated); connect(m_content, &InformationPanelContent::configurationFinished, this, [this]() { m_inConfigurationMode = false; }); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_content); m_initialized = true; } diff --git a/src/panels/information/informationpanelcontent.cpp b/src/panels/information/informationpanelcontent.cpp index 417ca709c..5c1b7ae22 100644 --- a/src/panels/information/informationpanelcontent.cpp +++ b/src/panels/information/informationpanelcontent.cpp @@ -1,491 +1,492 @@ /*************************************************************************** * Copyright (C) 2009 by Peter Penz * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "informationpanelcontent.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dolphin_informationpanelsettings.h" #include "phononwidget.h" #include "pixmapviewer.h" const int PLAY_ARROW_SIZE = 24; const int PLAY_ARROW_BORDER_SIZE = 2; InformationPanelContent::InformationPanelContent(QWidget* parent) : QWidget(parent), m_item(), m_previewJob(nullptr), m_outdatedPreviewTimer(nullptr), m_preview(nullptr), m_phononWidget(nullptr), m_nameLabel(nullptr), m_metaDataWidget(nullptr), m_metaDataArea(nullptr), m_placesItemModel(nullptr), m_isVideo(false) { parent->installEventFilter(this); // Initialize timer for disabling an outdated preview with a small // delay. This prevents flickering if the new preview can be generated // within a very small timeframe. m_outdatedPreviewTimer = new QTimer(this); m_outdatedPreviewTimer->setInterval(300); m_outdatedPreviewTimer->setSingleShot(true); connect(m_outdatedPreviewTimer, &QTimer::timeout, this, &InformationPanelContent::markOutdatedPreview); QVBoxLayout* layout = new QVBoxLayout(this); // preview const int minPreviewWidth = KIconLoader::SizeEnormous + KIconLoader::SizeMedium; m_preview = new PixmapViewer(parent); m_preview->setMinimumWidth(minPreviewWidth); m_preview->setMinimumHeight(KIconLoader::SizeEnormous); m_phononWidget = new PhononWidget(parent); m_phononWidget->hide(); m_phononWidget->setMinimumWidth(minPreviewWidth); m_phononWidget->setAutoPlay(InformationPanelSettings::previewsAutoPlay()); connect(m_phononWidget, &PhononWidget::hasVideoChanged, this, &InformationPanelContent::slotHasVideoChanged); // name m_nameLabel = new QLabel(parent); QFont font = m_nameLabel->font(); font.setBold(true); m_nameLabel->setFont(font); m_nameLabel->setTextFormat(Qt::PlainText); m_nameLabel->setAlignment(Qt::AlignHCenter); m_nameLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); const bool previewsShown = InformationPanelSettings::previewsShown(); m_preview->setVisible(previewsShown); m_metaDataWidget = new Baloo::FileMetaDataWidget(parent); m_metaDataWidget->setDateFormat(static_cast(InformationPanelSettings::dateFormat())); connect(m_metaDataWidget, &Baloo::FileMetaDataWidget::urlActivated, this, &InformationPanelContent::urlActivated); m_metaDataWidget->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); m_metaDataWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); // Configuration m_configureLabel = new QLabel(i18nc("@label::textbox", "Select which data should be shown:"), this); m_configureLabel->setWordWrap(true); m_configureLabel->setVisible(false); m_configureButtons = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel); m_configureButtons->setVisible(false); connect(m_configureButtons, &QDialogButtonBox::accepted, this, [this]() { m_metaDataWidget->setConfigurationMode(Baloo::ConfigurationMode::Accept); m_configureButtons->setVisible(false); m_configureLabel->setVisible(false); emit configurationFinished(); } ); connect(m_configureButtons, &QDialogButtonBox::rejected, this, [this]() { m_metaDataWidget->setConfigurationMode(Baloo::ConfigurationMode::Cancel); m_configureButtons->setVisible(false); m_configureLabel->setVisible(false); emit configurationFinished(); } ); m_metaDataArea = new QScrollArea(parent); m_metaDataArea->setWidget(m_metaDataWidget); m_metaDataArea->setWidgetResizable(true); m_metaDataArea->setFrameShape(QFrame::NoFrame); QWidget* viewport = m_metaDataArea->viewport(); viewport->installEventFilter(this); layout->addWidget(m_preview); layout->addWidget(m_phononWidget); layout->addWidget(m_nameLabel); layout->addWidget(new KSeparator()); layout->addWidget(m_configureLabel); layout->addWidget(m_metaDataArea); layout->addWidget(m_configureButtons); m_placesItemModel = new PlacesItemModel(this); } InformationPanelContent::~InformationPanelContent() { InformationPanelSettings::self()->save(); } void InformationPanelContent::showItem(const KFileItem& item) { - if (item != m_item) { + // compares item entries, comparing items only compares urls + if (m_item.entry() != item.entry()) { m_item = item; - m_preview->stopAnimatedImage(); refreshMetaData(); } + refreshPreview(); } void InformationPanelContent::refreshPixmapView() { // If there is a preview job, kill it to prevent that we have jobs for // multiple items running, and thus a race condition (bug 250787). if (m_previewJob) { m_previewJob->kill(); } // try to get a preview pixmap from the item... // Mark the currently shown preview as outdated. This is done // with a small delay to prevent a flickering when the next preview // can be shown within a short timeframe. This timer is not started // for directories, as directory previews might fail and return the // same icon. if (!m_item.isDir()) { m_outdatedPreviewTimer->start(); } QStringList plugins = KIO::PreviewJob::availablePlugins(); m_previewJob = new KIO::PreviewJob(KFileItemList() << m_item, QSize(m_preview->width(), m_preview->height()), &plugins); m_previewJob->setScaleType(KIO::PreviewJob::Unscaled); m_previewJob->setIgnoreMaximumSize(m_item.isLocalFile()); if (m_previewJob->uiDelegate()) { KJobWidgets::setWindow(m_previewJob, this); } connect(m_previewJob.data(), &KIO::PreviewJob::gotPreview, this, &InformationPanelContent::showPreview); connect(m_previewJob.data(), &KIO::PreviewJob::failed, this, &InformationPanelContent::showIcon); } void InformationPanelContent::refreshPreview() { // If there is a preview job, kill it to prevent that we have jobs for // multiple items running, and thus a race condition (bug 250787). if (m_previewJob) { m_previewJob->kill(); } m_preview->setCursor(Qt::ArrowCursor); bool usePhonon = false; setNameLabelText(m_item.text()); if (InformationPanelSettings::previewsShown()) { const QUrl itemUrl = m_item.url(); const bool isSearchUrl = itemUrl.scheme().contains(QLatin1String("search")) && m_item.localPath().isEmpty(); if (isSearchUrl) { m_preview->show(); // in the case of a search-URL the URL is not readable for humans // (at least not useful to show in the Information Panel) m_preview->setPixmap( QIcon::fromTheme(QStringLiteral("baloo")).pixmap(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous) ); } else { refreshPixmapView(); const QString mimeType = m_item.mimetype(); const bool isAnimatedImage = m_preview->isAnimatedImage(itemUrl.toLocalFile()); m_isVideo = !isAnimatedImage && mimeType.startsWith(QLatin1String("video/")); usePhonon = m_isVideo || mimeType.startsWith(QLatin1String("audio/")); if (usePhonon) { // change the cursor of the preview m_preview->setCursor(Qt::PointingHandCursor); m_preview->installEventFilter(m_phononWidget); // if the video is playing, has been paused or stopped // we don't need to update the preview/phonon widget states // unless the previewed file has changed, // or the setting previewshown has changed if ((m_phononWidget->state() != Phonon::State::PlayingState && m_phononWidget->state() != Phonon::State::PausedState && m_phononWidget->state() != Phonon::State::StoppedState) || m_item.targetUrl() != m_phononWidget->url() || (!m_preview->isVisible() &&! m_phononWidget->isVisible())) { if (InformationPanelSettings::previewsAutoPlay() && m_isVideo) { // hides the preview now to avoid flickering when the autoplay video starts m_preview->hide(); } else { // the video won't play before the preview is displayed m_preview->show(); } m_phononWidget->show(); m_phononWidget->setUrl(m_item.targetUrl(), m_isVideo ? PhononWidget::MediaKind::Video : PhononWidget::MediaKind::Audio); adjustWidgetSizes(parentWidget()->width()); } } else { if (isAnimatedImage) { m_preview->setAnimatedImageFileName(itemUrl.toLocalFile()); } // When we don't need it, hide the phonon widget first to avoid flickering m_phononWidget->hide(); m_preview->show(); m_preview->removeEventFilter(m_phononWidget); m_phononWidget->clearUrl(); } } } else { m_preview->stopAnimatedImage(); m_preview->hide(); m_phononWidget->hide(); } } void InformationPanelContent::configureShownProperties() { m_configureLabel->setVisible(true); m_configureButtons->setVisible(true); m_metaDataWidget->setConfigurationMode(Baloo::ConfigurationMode::ReStart); } void InformationPanelContent::refreshMetaData() { m_metaDataWidget->setDateFormat(static_cast(InformationPanelSettings::dateFormat())); m_metaDataWidget->show(); m_metaDataWidget->setItems(KFileItemList() << m_item); } void InformationPanelContent::showItems(const KFileItemList& items) { // If there is a preview job, kill it to prevent that we have jobs for // multiple items running, and thus a race condition (bug 250787). if (m_previewJob) { m_previewJob->kill(); } m_preview->stopAnimatedImage(); m_preview->setPixmap( QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous) ); setNameLabelText(i18ncp("@label", "%1 item selected", "%1 items selected", items.count())); m_metaDataWidget->setItems(items); m_phononWidget->hide(); m_item = KFileItem(); } bool InformationPanelContent::eventFilter(QObject* obj, QEvent* event) { switch (event->type()) { case QEvent::Resize: { QResizeEvent* resizeEvent = static_cast(event); if (obj == m_metaDataArea->viewport()) { // The size of the meta text area has changed. Adjust the fixed // width in a way that no horizontal scrollbar needs to be shown. m_metaDataWidget->setFixedWidth(resizeEvent->size().width()); } else if (obj == parent()) { adjustWidgetSizes(resizeEvent->size().width()); } break; } case QEvent::Polish: adjustWidgetSizes(parentWidget()->width()); break; case QEvent::FontChange: m_metaDataWidget->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); break; default: break; } return QWidget::eventFilter(obj, event); } void InformationPanelContent::showIcon(const KFileItem& item) { m_outdatedPreviewTimer->stop(); QPixmap pixmap = QIcon::fromTheme(item.iconName()).pixmap(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous); KIconLoader::global()->drawOverlays(item.overlays(), pixmap, KIconLoader::Desktop); m_preview->setPixmap(pixmap); } void InformationPanelContent::showPreview(const KFileItem& item, const QPixmap& pixmap) { m_outdatedPreviewTimer->stop(); QPixmap p = pixmap; KIconLoader::global()->drawOverlays(item.overlays(), p, KIconLoader::Desktop); if (m_isVideo) { // adds a play arrow // compute relative pixel positions const int zeroX = static_cast(p.width() / 2 - PLAY_ARROW_SIZE / 2 / devicePixelRatio()); const int zeroY = static_cast(p.height() / 2 - PLAY_ARROW_SIZE / 2 / devicePixelRatio()); QPolygon arrow; arrow << QPoint(zeroX, zeroY); arrow << QPoint(zeroX, zeroY + PLAY_ARROW_SIZE); arrow << QPoint(zeroX + PLAY_ARROW_SIZE, zeroY + PLAY_ARROW_SIZE / 2); QPainterPath path; path.addPolygon(arrow); QLinearGradient gradient(QPointF(zeroX, zeroY), QPointF(zeroX + PLAY_ARROW_SIZE,zeroY + PLAY_ARROW_SIZE)); QColor whiteColor = Qt::white; QColor blackColor = Qt::black; gradient.setColorAt(0, whiteColor); gradient.setColorAt(1, blackColor); QBrush brush(gradient); QPainter painter(&p); QPen pen(blackColor, PLAY_ARROW_BORDER_SIZE, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing); painter.drawPolygon(arrow); painter.fillPath(path, brush); } m_preview->setPixmap(p); } void InformationPanelContent::markOutdatedPreview() { KIconEffect *iconEffect = KIconLoader::global()->iconEffect(); QPixmap disabledPixmap = iconEffect->apply(m_preview->pixmap(), KIconLoader::Desktop, KIconLoader::DisabledState); m_preview->setPixmap(disabledPixmap); } KFileItemList InformationPanelContent::items() { return m_metaDataWidget->items(); } void InformationPanelContent::slotHasVideoChanged(bool hasVideo) { m_preview->setVisible(InformationPanelSettings::previewsShown() && !hasVideo); if (m_preview->isVisible() && m_preview->size().width() != m_preview->pixmap().size().width()) { // in case the information panel has been resized when the preview was not displayed // we need to refresh its content refreshPixmapView(); } } void InformationPanelContent::setPreviewAutoPlay(bool autoPlay) { m_phononWidget->setAutoPlay(autoPlay); } void InformationPanelContent::setNameLabelText(const QString& text) { QTextOption textOption; textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); const QString processedText = Qt::mightBeRichText(text) ? text : KStringHandler::preProcessWrap(text); QTextLayout textLayout(processedText); textLayout.setFont(m_nameLabel->font()); textLayout.setTextOption(textOption); QString wrappedText; wrappedText.reserve(processedText.length()); // wrap the text to fit into the width of m_nameLabel textLayout.beginLayout(); QTextLine line = textLayout.createLine(); while (line.isValid()) { line.setLineWidth(m_nameLabel->width()); wrappedText += processedText.midRef(line.textStart(), line.textLength()); line = textLayout.createLine(); if (line.isValid()) { wrappedText += QChar::LineSeparator; } } textLayout.endLayout(); m_nameLabel->setText(wrappedText); } void InformationPanelContent::adjustWidgetSizes(int width) { // If the text inside the name label or the info label cannot // get wrapped, then the maximum width of the label is increased // so that the width of the information panel gets increased. // To prevent this, the maximum width is adjusted to // the current width of the panel. const int maxWidth = width - style()->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Horizontal) * 4; m_nameLabel->setMaximumWidth(maxWidth); // The metadata widget also contains a text widget which may return // a large preferred width. m_metaDataWidget->setMaximumWidth(maxWidth); // try to increase the preview as large as possible m_preview->setSizeHint(QSize(maxWidth, maxWidth)); if (m_phononWidget->isVisible()) { // assure that the size of the video player is the same as the preview size m_phononWidget->setVideoSize(QSize(maxWidth, maxWidth)); } }