diff --git a/CMakeLists.txt b/CMakeLists.txt index 90c1df10e..88ccca256 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 "19") set (KDE_APPLICATIONS_VERSION_MINOR "11") 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.61.0") +set(KF5_MIN_VERSION "5.63.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/CMakeLists.txt b/src/CMakeLists.txt index 8bbb081cd..14701a1f4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,414 +1,415 @@ include(ECMAddAppIcon) configure_file(config-baloo.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-baloo.h) configure_file(config-kactivities.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kactivities.h) configure_file(config-terminal.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-terminal.h) add_definitions( -DTRANSLATION_DOMAIN=\"dolphin\" ) remove_definitions( -DQT_NO_CAST_FROM_BYTEARRAY -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII ) ########################################## set(dolphinvcs_LIB_SRCS views/versioncontrol/kversioncontrolplugin.cpp ) add_library(dolphinvcs ${dolphinvcs_LIB_SRCS}) generate_export_header(dolphinvcs BASE_NAME dolphinvcs) target_link_libraries( dolphinvcs PUBLIC Qt5::Widgets ) set_target_properties(dolphinvcs PROPERTIES VERSION ${DOLPHINVCS_VERSION_STRING} SOVERSION ${DOLPHINVCS_SOVERSION} EXPORT_NAME DolphinVcs ) ecm_generate_headers(dolphinvcs_LIB_HEADERS HEADER_NAMES KVersionControlPlugin RELATIVE "views/versioncontrol" REQUIRED_HEADERS dolphinvcs_LIB_HEADERS ) install(TARGETS dolphinvcs EXPORT DolphinVcsTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES views/versioncontrol/fileviewversioncontrolplugin.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dolphinvcs_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR} COMPONENT Devel) install(FILES ${dolphinvcs_LIB_HEADERS} DESTINATION "${KDE_INSTALL_INCLUDEDIR}/Dolphin" COMPONENT Devel) ########### next target ############### set(dolphinprivate_LIB_SRCS kitemviews/kfileitemlistview.cpp kitemviews/kfileitemlistwidget.cpp kitemviews/kfileitemmodel.cpp kitemviews/kfileitemmodelrolesupdater.cpp kitemviews/kitemlistcontainer.cpp kitemviews/kitemlistcontroller.cpp kitemviews/kitemlistgroupheader.cpp kitemviews/kitemlistheader.cpp kitemviews/kitemlistselectionmanager.cpp kitemviews/kitemliststyleoption.cpp kitemviews/kitemlistview.cpp kitemviews/kitemlistviewaccessible.cpp kitemviews/kitemlistwidget.cpp kitemviews/kitemmodelbase.cpp kitemviews/kitemset.cpp kitemviews/kstandarditem.cpp kitemviews/kstandarditemlistgroupheader.cpp kitemviews/kstandarditemlistwidget.cpp kitemviews/kstandarditemlistview.cpp kitemviews/kstandarditemmodel.cpp kitemviews/private/kdirectorycontentscounter.cpp kitemviews/private/kdirectorycontentscounterworker.cpp kitemviews/private/kfileitemclipboard.cpp kitemviews/private/kfileitemmodeldirlister.cpp kitemviews/private/kfileitemmodelfilter.cpp kitemviews/private/kitemlistheaderwidget.cpp kitemviews/private/kitemlistkeyboardsearchmanager.cpp kitemviews/private/kitemlistroleeditor.cpp kitemviews/private/kitemlistrubberband.cpp kitemviews/private/kitemlistselectiontoggle.cpp kitemviews/private/kitemlistsizehintresolver.cpp kitemviews/private/kitemlistsmoothscroller.cpp kitemviews/private/kitemlistviewanimation.cpp kitemviews/private/kitemlistviewlayouter.cpp kitemviews/private/kpixmapmodifier.cpp settings/applyviewpropsjob.cpp settings/viewmodes/viewmodesettings.cpp settings/viewpropertiesdialog.cpp settings/viewpropsprogressinfo.cpp views/dolphinfileitemlistwidget.cpp views/dolphinitemlistview.cpp views/dolphinnewfilemenuobserver.cpp views/dolphinremoteencoding.cpp views/dolphinview.cpp views/dolphinviewactionhandler.cpp views/draganddrophelper.cpp views/renamedialog.cpp views/versioncontrol/updateitemstatesthread.cpp views/versioncontrol/versioncontrolobserver.cpp views/viewmodecontroller.cpp views/viewproperties.cpp views/zoomlevelinfo.cpp dolphinremoveaction.cpp middleclickactioneventfilter.cpp dolphinnewfilemenu.cpp ) ecm_qt_declare_logging_category(dolphinprivate_LIB_SRCS HEADER dolphindebug.h IDENTIFIER DolphinDebug CATEGORY_NAME org.kde.dolphin) if(HAVE_BALOO) set(dolphinprivate_LIB_SRCS ${dolphinprivate_LIB_SRCS} views/tooltips/dolphinfilemetadatawidget.cpp views/tooltips/tooltipmanager.cpp kitemviews/private/kbaloorolesprovider.cpp ) endif() kconfig_add_kcfg_files(dolphinprivate_LIB_SRCS GENERATE_MOC settings/dolphin_compactmodesettings.kcfgc settings/dolphin_directoryviewpropertysettings.kcfgc settings/dolphin_detailsmodesettings.kcfgc settings/dolphin_iconsmodesettings.kcfgc settings/dolphin_generalsettings.kcfgc settings/dolphin_versioncontrolsettings.kcfgc ) add_library(dolphinprivate ${dolphinprivate_LIB_SRCS}) generate_export_header(dolphinprivate BASE_NAME dolphin) target_link_libraries( dolphinprivate PUBLIC dolphinvcs Qt5::Concurrent Qt5::Gui KF5::I18n KF5::IconThemes KF5::KIOCore KF5::KIOWidgets KF5::KIOFileWidgets KF5::Completion KF5::TextWidgets KF5::ConfigCore KF5::NewStuff KF5::Parts KF5::WindowSystem ) if(HAVE_BALOO) target_link_libraries( dolphinprivate PUBLIC KF5::FileMetaData KF5::Baloo KF5::BalooWidgets ) endif() set_target_properties(dolphinprivate PROPERTIES VERSION ${DOLPHINPRIVATE_VERSION_STRING} SOVERSION ${DOLPHINPRIVATE_SOVERSION} ) install(TARGETS dolphinprivate ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dolphin_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR} COMPONENT Devel) ########################################## set(dolphinpart_SRCS dolphinpart.cpp dolphinpart_ext.cpp dolphindebug.cpp ) qt5_add_resources(dolphinpart_SRCS dolphinpart.qrc) add_library(dolphinpart MODULE ${dolphinpart_SRCS}) target_link_libraries(dolphinpart dolphinprivate ) install(TARGETS dolphinpart DESTINATION ${KDE_INSTALL_PLUGINDIR}) install(FILES dolphinpart.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) ########################################## set(dolphinstatic_SRCS dolphinbookmarkhandler.cpp dolphindockwidget.cpp dolphinmainwindow.cpp dolphinviewcontainer.cpp dolphincontextmenu.cpp dolphintabbar.cpp dolphinplacesmodelsingleton.cpp dolphinrecenttabsmenu.cpp dolphintabpage.cpp dolphintabwidget.cpp trash/dolphintrash.cpp filterbar/filterbar.cpp panels/places/placespanel.cpp panels/places/placesitem.cpp panels/places/placesitemlistgroupheader.cpp panels/places/placesitemlistwidget.cpp panels/places/placesitemmodel.cpp panels/places/placesitemsignalhandler.cpp panels/places/placesview.cpp panels/panel.cpp panels/folders/foldersitemlistwidget.cpp panels/folders/treeviewcontextmenu.cpp panels/folders/folderspanel.cpp panels/terminal/terminalpanel.cpp search/dolphinfacetswidget.cpp search/dolphinsearchbox.cpp settings/general/behaviorsettingspage.cpp settings/general/configurepreviewplugindialog.cpp settings/general/confirmationssettingspage.cpp settings/general/generalsettingspage.cpp settings/general/previewssettingspage.cpp settings/general/statusbarsettingspage.cpp settings/dolphinsettingsdialog.cpp settings/navigation/navigationsettingspage.cpp settings/services/servicessettingspage.cpp settings/settingspagebase.cpp settings/serviceitemdelegate.cpp settings/servicemodel.cpp settings/startup/startupsettingspage.cpp settings/trash/trashsettingspage.cpp settings/viewmodes/dolphinfontrequester.cpp settings/viewmodes/viewsettingspage.cpp settings/viewmodes/viewmodesettings.cpp settings/viewmodes/viewsettingstab.cpp statusbar/dolphinstatusbar.cpp statusbar/mountpointobserver.cpp statusbar/mountpointobservercache.cpp statusbar/spaceinfoobserver.cpp statusbar/statusbarspaceinfo.cpp views/zoomlevelinfo.cpp dolphindebug.cpp global.cpp ) if(HAVE_BALOO) set(dolphinstatic_SRCS ${dolphinstatic_SRCS} panels/information/informationpanel.cpp panels/information/informationpanelcontent.cpp panels/information/pixmapviewer.cpp panels/information/phononwidget.cpp ) endif() kconfig_add_kcfg_files(dolphinstatic_SRCS GENERATE_MOC panels/folders/dolphin_folderspanelsettings.kcfgc panels/information/dolphin_informationpanelsettings.kcfgc panels/places/dolphin_placespanelsettings.kcfgc settings/dolphin_compactmodesettings.kcfgc settings/dolphin_detailsmodesettings.kcfgc settings/dolphin_generalsettings.kcfgc settings/dolphin_iconsmodesettings.kcfgc search/dolphin_searchsettings.kcfgc settings/dolphin_versioncontrolsettings.kcfgc ) qt5_add_resources(dolphinstatic_SRCS dolphin.qrc) add_library(dolphinstatic STATIC ${dolphinstatic_SRCS}) target_include_directories(dolphinstatic SYSTEM PRIVATE ${PHONON_INCLUDES}) target_link_libraries(dolphinstatic dolphinprivate + KF5::CoreAddons KF5::KCMUtils KF5::DBusAddons KF5::Notifications Phonon::phonon4qt5 ) if (HAVE_KACTIVITIES) target_link_libraries( dolphinstatic KF5::Activities ) endif() set(dolphin_SRCS dbusinterface.cpp main.cpp ) # Sets the icon on Windows and OSX file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*system-file-manager.png") ecm_add_app_icon(dolphin_SRCS ICONS ${ICONS_SRCS}) kf5_add_kdeinit_executable(dolphin ${dolphin_SRCS}) target_link_libraries(kdeinit_dolphin PRIVATE dolphinstatic dolphinprivate KF5::Crash ) include(DbusInterfaceMacros) generate_and_install_dbus_interface( kdeinit_dolphin dbusinterface.h org.freedesktop.FileManager1.xml OPTIONS -a ) install(TARGETS kdeinit_dolphin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(TARGETS dolphin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########################################## set(kcm_dolphinviewmodes_PART_SRCS settings/kcm/kcmdolphinviewmodes.cpp settings/viewmodes/dolphinfontrequester.cpp settings/viewmodes/viewmodesettings.cpp settings/viewmodes/viewsettingstab.cpp views/zoomlevelinfo.cpp) set(kcm_dolphinnavigation_PART_SRCS settings/kcm/kcmdolphinnavigation.cpp settings/navigation/navigationsettingspage.cpp settings/settingspagebase.cpp) set(kcm_dolphinservices_PART_SRCS settings/kcm/kcmdolphinservices.cpp settings/services/servicessettingspage.cpp settings/settingspagebase.cpp settings/serviceitemdelegate.cpp settings/servicemodel.cpp) set(kcm_dolphingeneral_PART_SRCS settings/kcm/kcmdolphingeneral.cpp settings/general/behaviorsettingspage.cpp settings/general/previewssettingspage.cpp settings/general/configurepreviewplugindialog.cpp settings/general/confirmationssettingspage.cpp settings/settingspagebase.cpp settings/serviceitemdelegate.cpp settings/servicemodel.cpp) kconfig_add_kcfg_files(kcm_dolphinviewmodes_PART_SRCS settings/dolphin_compactmodesettings.kcfgc settings/dolphin_directoryviewpropertysettings.kcfgc settings/dolphin_detailsmodesettings.kcfgc settings/dolphin_iconsmodesettings.kcfgc settings/dolphin_generalsettings.kcfgc settings/dolphin_versioncontrolsettings.kcfgc ) kconfig_add_kcfg_files(kcm_dolphinnavigation_PART_SRCS settings/dolphin_generalsettings.kcfgc) kconfig_add_kcfg_files(kcm_dolphinservices_PART_SRCS settings/dolphin_generalsettings.kcfgc settings/dolphin_versioncontrolsettings.kcfgc) kconfig_add_kcfg_files(kcm_dolphingeneral_PART_SRCS settings/dolphin_generalsettings.kcfgc) add_library(kcm_dolphinviewmodes MODULE ${kcm_dolphinviewmodes_PART_SRCS}) add_library(kcm_dolphinnavigation MODULE ${kcm_dolphinnavigation_PART_SRCS}) add_library(kcm_dolphinservices MODULE ${kcm_dolphinservices_PART_SRCS}) add_library(kcm_dolphingeneral MODULE ${kcm_dolphingeneral_PART_SRCS}) target_link_libraries(kcm_dolphinviewmodes dolphinprivate) target_link_libraries(kcm_dolphinnavigation dolphinprivate) target_link_libraries(kcm_dolphinservices dolphinprivate) target_link_libraries(kcm_dolphingeneral dolphinprivate) install(TARGETS kcm_dolphinviewmodes DESTINATION ${KDE_INSTALL_PLUGINDIR} ) install(TARGETS kcm_dolphinnavigation DESTINATION ${KDE_INSTALL_PLUGINDIR} ) install(TARGETS kcm_dolphinservices DESTINATION ${KDE_INSTALL_PLUGINDIR} ) install(TARGETS kcm_dolphingeneral DESTINATION ${KDE_INSTALL_PLUGINDIR} ) add_subdirectory(settings/services/servicemenuinstaller) ########### install files ############### install( PROGRAMS org.kde.dolphin.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install( DIRECTORY DESTINATION "${KDE_INSTALL_FULL_DATAROOTDIR}/kglobalaccel" ) install( CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink \"${KDE_INSTALL_FULL_APPDIR}/org.kde.dolphin.desktop\" \"\$ENV{DESTDIR}${KDE_INSTALL_FULL_DATAROOTDIR}/kglobalaccel/org.kde.dolphin.desktop\")" ) install( FILES settings/dolphin_directoryviewpropertysettings.kcfg settings/dolphin_generalsettings.kcfg settings/dolphin_compactmodesettings.kcfg settings/dolphin_iconsmodesettings.kcfg settings/dolphin_detailsmodesettings.kcfg settings/dolphin_versioncontrolsettings.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR} ) install( FILES org.kde.dolphin.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) install( FILES settings/kcm/kcmdolphinviewmodes.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES settings/kcm/kcmdolphinnavigation.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES settings/kcm/kcmdolphinservices.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES settings/kcm/kcmdolphingeneral.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES settings/services/servicemenu.knsrc DESTINATION ${KDE_INSTALL_CONFDIR} ) if(BUILD_TESTING) find_package(Qt5Test ${QT_MIN_VERSION} CONFIG REQUIRED) add_subdirectory(tests) endif() diff --git a/src/panels/places/placesitemmodel.cpp b/src/panels/places/placesitemmodel.cpp index e8636942b..c0cef9315 100644 --- a/src/panels/places/placesitemmodel.cpp +++ b/src/panels/places/placesitemmodel.cpp @@ -1,776 +1,800 @@ /*************************************************************************** * Copyright (C) 2012 by Peter Penz * * * * Based on KFilePlacesModel from kdelibs: * * Copyright (C) 2007 Kevin Ottens * * Copyright (C) 2007 David Faure * * * * 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 "placesitemmodel.h" #include "dolphin_generalsettings.h" #include "dolphindebug.h" #include "dolphinplacesmodelsingleton.h" #include "placesitem.h" #include "placesitemsignalhandler.h" #include "views/dolphinview.h" #include "views/viewproperties.h" #include #include #include #include #include +#include +#include #include #include #include #include namespace { static QList balooURLs = { QUrl(QStringLiteral("timeline:/today")), QUrl(QStringLiteral("timeline:/yesterday")), QUrl(QStringLiteral("timeline:/thismonth")), QUrl(QStringLiteral("timeline:/lastmonth")), QUrl(QStringLiteral("search:/documents")), QUrl(QStringLiteral("search:/images")), QUrl(QStringLiteral("search:/audio")), QUrl(QStringLiteral("search:/videos")) }; } PlacesItemModel::PlacesItemModel(QObject* parent) : KStandardItemModel(parent), m_hiddenItemsShown(false), m_deviceToTearDown(nullptr), m_storageSetupInProgress(), m_sourceModel(DolphinPlacesModelSingleton::instance().placesModel()) { cleanupBookmarks(); loadBookmarks(); initializeDefaultViewProperties(); connect(m_sourceModel, &KFilePlacesModel::rowsInserted, this, &PlacesItemModel::onSourceModelRowsInserted); connect(m_sourceModel, &KFilePlacesModel::rowsAboutToBeRemoved, this, &PlacesItemModel::onSourceModelRowsAboutToBeRemoved); connect(m_sourceModel, &KFilePlacesModel::dataChanged, this, &PlacesItemModel::onSourceModelDataChanged); connect(m_sourceModel, &KFilePlacesModel::rowsAboutToBeMoved, this, &PlacesItemModel::onSourceModelRowsAboutToBeMoved); connect(m_sourceModel, &KFilePlacesModel::rowsMoved, this, &PlacesItemModel::onSourceModelRowsMoved); connect(m_sourceModel, &KFilePlacesModel::groupHiddenChanged, this, &PlacesItemModel::onSourceModelGroupHiddenChanged); } PlacesItemModel::~PlacesItemModel() { } void PlacesItemModel::createPlacesItem(const QString &text, const QUrl &url, const QString &iconName, const QString &appName) { createPlacesItem(text, url, iconName, appName, -1); } void PlacesItemModel::createPlacesItem(const QString &text, const QUrl &url, const QString &iconName, const QString &appName, int after) { m_sourceModel->addPlace(text, url, iconName, appName, mapToSource(after)); } PlacesItem* PlacesItemModel::placesItem(int index) const { return dynamic_cast(item(index)); } int PlacesItemModel::hiddenCount() const { return m_sourceModel->hiddenCount(); } void PlacesItemModel::setHiddenItemsShown(bool show) { if (m_hiddenItemsShown == show) { return; } m_hiddenItemsShown = show; if (show) { for (int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { const QModelIndex index = m_sourceModel->index(r, 0); if (!m_sourceModel->isHidden(index)) { continue; } addItemFromSourceModel(index); } } else { for (int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { const QModelIndex index = m_sourceModel->index(r, 0); if (m_sourceModel->isHidden(index)) { removeItemByIndex(index); } } } } bool PlacesItemModel::hiddenItemsShown() const { return m_hiddenItemsShown; } int PlacesItemModel::closestItem(const QUrl& url) const { return mapFromSource(m_sourceModel->closestItem(url)); } // look for the correct position for the item based on source model void PlacesItemModel::insertSortedItem(PlacesItem* item) { if (!item) { return; } const KBookmark iBookmark = item->bookmark(); const QString iBookmarkId = bookmarkId(iBookmark); QModelIndex sourceIndex; int pos = 0; for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { sourceIndex = m_sourceModel->index(r, 0); const KBookmark sourceBookmark = m_sourceModel->bookmarkForIndex(sourceIndex); if (bookmarkId(sourceBookmark) == iBookmarkId) { break; } if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) { pos++; } } m_indexMap.insert(pos, sourceIndex); insertItem(pos, item); } void PlacesItemModel::onItemInserted(int index) { KStandardItemModel::onItemInserted(index); } void PlacesItemModel::onItemRemoved(int index, KStandardItem* removedItem) { m_indexMap.removeAt(index); KStandardItemModel::onItemRemoved(index, removedItem); } void PlacesItemModel::onItemChanged(int index, const QSet& changedRoles) { const QModelIndex sourceIndex = mapToSource(index); const PlacesItem *changedItem = placesItem(mapFromSource(sourceIndex)); if (!changedItem || !sourceIndex.isValid()) { qWarning() << "invalid item changed signal"; return; } if (changedRoles.contains("isHidden")) { if (m_sourceModel->isHidden(sourceIndex) != changedItem->isHidden()) { m_sourceModel->setPlaceHidden(sourceIndex, changedItem->isHidden()); } else { m_sourceModel->refresh(); } } KStandardItemModel::onItemChanged(index, changedRoles); } QAction* PlacesItemModel::ejectAction(int index) const { const PlacesItem* item = placesItem(index); if (item && item->device().is()) { return new QAction(QIcon::fromTheme(QStringLiteral("media-eject")), i18nc("@item", "Eject"), nullptr); } return nullptr; } QAction* PlacesItemModel::teardownAction(int index) const { const PlacesItem* item = placesItem(index); if (!item) { return nullptr; } Solid::Device device = item->device(); const bool providesTearDown = device.is() && device.as()->isAccessible(); if (!providesTearDown) { return nullptr; } Solid::StorageDrive* drive = device.as(); if (!drive) { drive = device.parent().as(); } bool hotPluggable = false; bool removable = false; if (drive) { hotPluggable = drive->isHotpluggable(); removable = drive->isRemovable(); } QString iconName; QString text; if (device.is()) { text = i18nc("@item", "Release"); } else if (removable || hotPluggable) { text = i18nc("@item", "Safely Remove"); iconName = QStringLiteral("media-eject"); } else { text = i18nc("@item", "Unmount"); iconName = QStringLiteral("media-eject"); } if (iconName.isEmpty()) { return new QAction(text, nullptr); } return new QAction(QIcon::fromTheme(iconName), text, nullptr); } void PlacesItemModel::requestEject(int index) { const PlacesItem* item = placesItem(index); if (item) { Solid::OpticalDrive* drive = item->device().parent().as(); if (drive) { connect(drive, &Solid::OpticalDrive::ejectDone, this, &PlacesItemModel::slotStorageTearDownDone); drive->eject(); } else { const QString label = item->text(); const QString message = i18nc("@info", "The device '%1' is not a disk and cannot be ejected.", label); emit errorMessage(message); } } } void PlacesItemModel::requestTearDown(int index) { const PlacesItem* item = placesItem(index); if (item) { Solid::StorageAccess *tmp = item->device().as(); if (tmp) { m_deviceToTearDown = tmp; // disconnect the Solid::StorageAccess::teardownRequested // to prevent emitting PlacesItemModel::storageTearDownExternallyRequested // after we have emitted PlacesItemModel::storageTearDownRequested disconnect(tmp, &Solid::StorageAccess::teardownRequested, item->signalHandler(), &PlacesItemSignalHandler::onTearDownRequested); emit storageTearDownRequested(tmp->filePath()); } } } bool PlacesItemModel::storageSetupNeeded(int index) const { const PlacesItem* item = placesItem(index); return item ? item->storageSetupNeeded() : false; } void PlacesItemModel::requestStorageSetup(int index) { const PlacesItem* item = placesItem(index); if (!item) { return; } Solid::Device device = item->device(); const bool setup = device.is() && !m_storageSetupInProgress.contains(device.as()) && !device.as()->isAccessible(); if (setup) { Solid::StorageAccess* access = device.as(); m_storageSetupInProgress[access] = index; connect(access, &Solid::StorageAccess::setupDone, this, &PlacesItemModel::slotStorageSetupDone); access->setup(); } } QMimeData* PlacesItemModel::createMimeData(const KItemSet& indexes) const { QList urls; QByteArray itemData; QDataStream stream(&itemData, QIODevice::WriteOnly); for (int index : indexes) { const QUrl itemUrl = placesItem(index)->url(); if (itemUrl.isValid()) { urls << itemUrl; } stream << index; } QMimeData* mimeData = new QMimeData(); if (!urls.isEmpty()) { mimeData->setUrls(urls); } else { // #378954: prevent itemDropEvent() drops if there isn't a source url. mimeData->setData(blacklistItemDropEventMimeType(), QByteArrayLiteral("true")); } mimeData->setData(internalMimeType(), itemData); return mimeData; } bool PlacesItemModel::supportsDropping(int index) const { return index >= 0 && index < count(); } void PlacesItemModel::dropMimeDataBefore(int index, const QMimeData* mimeData) { if (mimeData->hasFormat(internalMimeType())) { // The item has been moved inside the view QByteArray itemData = mimeData->data(internalMimeType()); QDataStream stream(&itemData, QIODevice::ReadOnly); int oldIndex; stream >> oldIndex; QModelIndex sourceIndex = mapToSource(index); QModelIndex oldSourceIndex = mapToSource(oldIndex); m_sourceModel->movePlace(oldSourceIndex.row(), sourceIndex.row()); } else if (mimeData->hasFormat(QStringLiteral("text/uri-list"))) { // One or more items must be added to the model const QList urls = KUrlMimeData::urlsFromMimeData(mimeData); for (int i = urls.count() - 1; i >= 0; --i) { const QUrl& url = urls[i]; QString text = url.fileName(); if (text.isEmpty()) { text = url.host(); } if ((url.isLocalFile() && !QFileInfo(url.toLocalFile()).isDir()) || url.scheme() == QLatin1String("trash")) { // Only directories outside the trash are allowed continue; } createPlacesItem(text, url, KIO::iconNameForUrl(url), {}, qMax(0, index - 1)); } } // will save bookmark alteration and fix sort if that is broken by the drag/drop operation refresh(); } void PlacesItemModel::addItemFromSourceModel(const QModelIndex &index) { if (!m_hiddenItemsShown && m_sourceModel->isHidden(index)) { return; } const KBookmark bookmark = m_sourceModel->bookmarkForIndex(index); Q_ASSERT(!bookmark.isNull()); PlacesItem *item = new PlacesItem(bookmark); updateItem(item, index); insertSortedItem(item); if (m_sourceModel->isDevice(index)) { connect(item->signalHandler(), &PlacesItemSignalHandler::tearDownExternallyRequested, this, &PlacesItemModel::storageTearDownExternallyRequested); } } void PlacesItemModel::removeItemByIndex(const QModelIndex &sourceIndex) { QString id = bookmarkId(m_sourceModel->bookmarkForIndex(sourceIndex)); for (int i = 0, iMax = count(); i < iMax; ++i) { if (bookmarkId(placesItem(i)->bookmark()) == id) { removeItem(i); return; } } } QString PlacesItemModel::bookmarkId(const KBookmark &bookmark) const { QString id = bookmark.metaDataItem(QStringLiteral("UDI")); if (id.isEmpty()) { id = bookmark.metaDataItem(QStringLiteral("ID")); } return id; } void PlacesItemModel::initializeDefaultViewProperties() const { for(int i = 0, iMax = m_sourceModel->rowCount(); i < iMax; i++) { const QModelIndex index = m_sourceModel->index(i, 0); const PlacesItem *item = placesItem(mapFromSource(index)); if (!item) { continue; } // Create default view-properties for all "Search For" and "Recently Saved" bookmarks // in case the user has not already created custom view-properties for a corresponding // query yet. const bool createDefaultViewProperties = item->isSearchOrTimelineUrl() && !GeneralSettings::self()->globalViewProps(); if (createDefaultViewProperties) { const QUrl itemUrl = item->url(); ViewProperties props(KFilePlacesModel::convertedUrl(itemUrl)); if (!props.exist()) { const QString path = itemUrl.path(); if (path == QLatin1String("/documents")) { props.setViewMode(DolphinView::DetailsView); props.setPreviewsShown(false); props.setVisibleRoles({"text", "path"}); } else if (path == QLatin1String("/images")) { props.setViewMode(DolphinView::IconsView); props.setPreviewsShown(true); props.setVisibleRoles({"text", "height", "width"}); } else if (path == QLatin1String("/audio")) { props.setViewMode(DolphinView::DetailsView); props.setPreviewsShown(false); props.setVisibleRoles({"text", "artist", "album"}); } else if (path == QLatin1String("/videos")) { props.setViewMode(DolphinView::IconsView); props.setPreviewsShown(true); props.setVisibleRoles({"text"}); } else if (itemUrl.scheme() == QLatin1String("timeline")) { props.setViewMode(DolphinView::DetailsView); props.setVisibleRoles({"text", "modificationtime"}); } props.save(); } } } } void PlacesItemModel::updateItem(PlacesItem *item, const QModelIndex &index) { item->setGroup(index.data(KFilePlacesModel::GroupRole).toString()); item->setIcon(index.data(KFilePlacesModel::IconNameRole).toString()); item->setGroupHidden(index.data(KFilePlacesModel::GroupHiddenRole).toBool()); } void PlacesItemModel::slotStorageTearDownDone(Solid::ErrorType error, const QVariant& errorData) { if (error && errorData.isValid()) { - emit errorMessage(errorData.toString()); + if (error == Solid::ErrorType::DeviceBusy) { + KListOpenFilesJob* listOpenFilesJob = new KListOpenFilesJob(m_deviceToTearDown->filePath()); + connect(listOpenFilesJob, &KIO::Job::result, this, [this, listOpenFilesJob](KJob*) { + const KProcessList::KProcessInfoList blockingProcesses = listOpenFilesJob->processInfoList(); + QString errorString; + if (blockingProcesses.isEmpty()) { + errorString = i18n("One or more files on this device are open within an application."); + } else { + QStringList blockingApps; + for (const auto& process : blockingProcesses) { + blockingApps << process.name(); + } + blockingApps.removeDuplicates(); + errorString = xi18np("One or more files on this device are opened in application \"%2\".", + "One or more files on this device are opened in following applications: %2.", + blockingApps.count(), blockingApps.join(i18nc("separator in list of apps blocking device unmount", ", "))); + } + emit errorMessage(errorString); + }); + listOpenFilesJob->start(); + } else { + emit errorMessage(errorData.toString()); + } } disconnect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone, this, &PlacesItemModel::slotStorageTearDownDone); m_deviceToTearDown = nullptr; } void PlacesItemModel::slotStorageSetupDone(Solid::ErrorType error, const QVariant& errorData, const QString& udi) { Q_UNUSED(udi); const int index = m_storageSetupInProgress.take(sender()); const PlacesItem* item = placesItem(index); if (!item) { return; } if (error != Solid::NoError) { if (errorData.isValid()) { emit errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2", item->text(), errorData.toString())); } else { emit errorMessage(i18nc("@info", "An error occurred while accessing '%1'", item->text())); } emit storageSetupDone(index, false); } else { emit storageSetupDone(index, true); } } void PlacesItemModel::onSourceModelRowsInserted(const QModelIndex &parent, int first, int last) { for (int i = first; i <= last; i++) { const QModelIndex index = m_sourceModel->index(i, 0, parent); addItemFromSourceModel(index); } } void PlacesItemModel::onSourceModelRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) { for(int r = first; r <= last; r++) { const QModelIndex index = m_sourceModel->index(r, 0, parent); int oldIndex = mapFromSource(index); if (oldIndex != -1) { removeItem(oldIndex); } } } void PlacesItemModel::onSourceModelRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) { Q_UNUSED(destination); Q_UNUSED(row); for(int r = start; r <= end; r++) { const QModelIndex sourceIndex = m_sourceModel->index(r, 0, parent); // remove moved item removeItem(mapFromSource(sourceIndex)); } } void PlacesItemModel::onSourceModelRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) { Q_UNUSED(destination); Q_UNUSED(parent); const int blockSize = (end - start) + 1; for (int r = start; r <= end; r++) { // insert the moved item in the new position const int targetRow = row + (start - r) - (r < row ? blockSize : 0); const QModelIndex targetIndex = m_sourceModel->index(targetRow, 0, destination); addItemFromSourceModel(targetIndex); } } void PlacesItemModel::onSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { Q_UNUSED(roles); for (int r = topLeft.row(); r <= bottomRight.row(); r++) { const QModelIndex sourceIndex = m_sourceModel->index(r, 0); const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex); PlacesItem *placeItem = itemFromBookmark(bookmark); if (placeItem && (!m_hiddenItemsShown && m_sourceModel->isHidden(sourceIndex))) { //hide item if it became invisible removeItem(index(placeItem)); return; } if (!placeItem && (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex))) { //show item if it became visible addItemFromSourceModel(sourceIndex); return; } if (placeItem && !m_sourceModel->isDevice(sourceIndex)) { placeItem->setText(bookmark.text()); placeItem->setIcon(sourceIndex.data(KFilePlacesModel::IconNameRole).toString()); placeItem->setUrl(m_sourceModel->url(sourceIndex)); placeItem->bookmark().setMetaDataItem(QStringLiteral("OnlyInApp"), bookmark.metaDataItem(QStringLiteral("OnlyInApp"))); // must update the bookmark object placeItem->setBookmark(bookmark); } } } void PlacesItemModel::onSourceModelGroupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden) { const auto groupIndexes = m_sourceModel->groupIndexes(group); for (const QModelIndex &sourceIndex : groupIndexes) { PlacesItem *item = placesItem(mapFromSource(sourceIndex)); if (item) { item->setGroupHidden(hidden); } } } void PlacesItemModel::cleanupBookmarks() { // KIO model now provides support for baloo urls, and because of that we // need to remove old URLs that were visible only in Dolphin to avoid duplication int row = 0; do { const QModelIndex sourceIndex = m_sourceModel->index(row, 0); const KBookmark bookmark = m_sourceModel->bookmarkForIndex(sourceIndex); const QUrl url = bookmark.url(); const QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); if ((appName == KAboutData::applicationData().componentName() || appName == KAboutData::applicationData().componentName() + DolphinPlacesModelSingleton::applicationNameSuffix()) && balooURLs.contains(url)) { qCDebug(DolphinDebug) << "Removing old baloo url:" << url; m_sourceModel->removePlace(sourceIndex); } else { row++; } } while (row < m_sourceModel->rowCount()); } void PlacesItemModel::loadBookmarks() { for(int r = 0, rMax = m_sourceModel->rowCount(); r < rMax; r++) { const QModelIndex sourceIndex = m_sourceModel->index(r, 0); if (m_hiddenItemsShown || !m_sourceModel->isHidden(sourceIndex)) { addItemFromSourceModel(sourceIndex); } } } void PlacesItemModel::clear() { KStandardItemModel::clear(); } void PlacesItemModel::proceedWithTearDown() { Q_ASSERT(m_deviceToTearDown); connect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone, this, &PlacesItemModel::slotStorageTearDownDone); m_deviceToTearDown->teardown(); } void PlacesItemModel::deleteItem(int index) { QModelIndex sourceIndex = mapToSource(index); Q_ASSERT(sourceIndex.isValid()); m_sourceModel->removePlace(sourceIndex); } void PlacesItemModel::refresh() { m_sourceModel->refresh(); } void PlacesItemModel::hideItem(int index) { PlacesItem* shownItem = placesItem(index); if (!shownItem) { return; } shownItem->setHidden(true); } QString PlacesItemModel::internalMimeType() const { return "application/x-dolphinplacesmodel-" + QString::number((qptrdiff)this); } int PlacesItemModel::groupedDropIndex(int index, const PlacesItem* item) const { Q_ASSERT(item); int dropIndex = index; const QString group = item->group(); const int itemCount = count(); if (index < 0) { dropIndex = itemCount; } // Search nearest previous item with the same group int previousIndex = -1; for (int i = dropIndex - 1; i >= 0; --i) { if (placesItem(i)->group() == group) { previousIndex = i; break; } } // Search nearest next item with the same group int nextIndex = -1; for (int i = dropIndex; i < count(); ++i) { if (placesItem(i)->group() == group) { nextIndex = i; break; } } // Adjust the drop-index to be inserted to the // nearest item with the same group. if (previousIndex >= 0 && nextIndex >= 0) { dropIndex = (dropIndex - previousIndex < nextIndex - dropIndex) ? previousIndex + 1 : nextIndex; } else if (previousIndex >= 0) { dropIndex = previousIndex + 1; } else if (nextIndex >= 0) { dropIndex = nextIndex; } return dropIndex; } bool PlacesItemModel::equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2) { const QString udi1 = b1.metaDataItem(QStringLiteral("UDI")); const QString udi2 = b2.metaDataItem(QStringLiteral("UDI")); if (!udi1.isEmpty() && !udi2.isEmpty()) { return udi1 == udi2; } else { return b1.metaDataItem(QStringLiteral("ID")) == b2.metaDataItem(QStringLiteral("ID")); } } int PlacesItemModel::mapFromSource(const QModelIndex &index) const { if (!index.isValid()) { return -1; } return m_indexMap.indexOf(index); } bool PlacesItemModel::isDir(int index) const { Q_UNUSED(index); return true; } KFilePlacesModel::GroupType PlacesItemModel::groupType(int row) const { return m_sourceModel->groupType(mapToSource(row)); } bool PlacesItemModel::isGroupHidden(KFilePlacesModel::GroupType type) const { return m_sourceModel->isGroupHidden(type); } void PlacesItemModel::setGroupHidden(KFilePlacesModel::GroupType type, bool hidden) { return m_sourceModel->setGroupHidden(type, hidden); } QModelIndex PlacesItemModel::mapToSource(int row) const { return m_indexMap.value(row); } PlacesItem *PlacesItemModel::itemFromBookmark(const KBookmark &bookmark) const { const QString id = bookmarkId(bookmark); for (int i = 0, iMax = count(); i < iMax; i++) { PlacesItem *item = placesItem(i); const KBookmark itemBookmark = item->bookmark(); if (bookmarkId(itemBookmark) == id) { return item; } } return nullptr; }