]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Merge branch 'release/21.12'
authorNicolas Fella <nicolas.fella@gmx.de>
Fri, 4 Feb 2022 11:58:29 +0000 (12:58 +0100)
committerNicolas Fella <nicolas.fella@gmx.de>
Fri, 4 Feb 2022 11:58:29 +0000 (12:58 +0100)
104 files changed:
.gitlab-ci.yml
.kde-ci.yml
CMakeLists.txt
DolphinVcsConfig.cmake.in
cmake/DbusInterfaceMacros.cmake
doc/index.docbook
src/CMakeLists.txt
src/dolphincontextmenu.cpp
src/dolphinmainwindow.cpp
src/dolphinmainwindow.h
src/dolphinnavigatorswidgetaction.h
src/dolphinpart.cpp
src/dolphinplacesmodelsingleton.cpp
src/dolphinplacesmodelsingleton.h
src/dolphintabbar.cpp
src/dolphintabpage.h
src/dolphinurlnavigatorscontroller.h
src/dolphinviewcontainer.cpp
src/dolphinviewcontainer.h
src/kitemviews/kfileitemlistwidget.cpp
src/kitemviews/kfileitemmodel.cpp
src/kitemviews/kfileitemmodel.h
src/kitemviews/kfileitemmodelrolesupdater.cpp
src/kitemviews/kitemlistcontroller.cpp
src/kitemviews/kitemlistcontroller.h
src/kitemviews/kitemlistgroupheader.cpp
src/kitemviews/kitemlistgroupheader.h
src/kitemviews/kitemlistheader.cpp
src/kitemviews/kitemlistheader.h
src/kitemviews/kitemlistview.cpp
src/kitemviews/kitemlistview.h
src/kitemviews/kitemlistviewaccessible.cpp
src/kitemviews/kitemlistwidget.cpp
src/kitemviews/kitemlistwidget.h
src/kitemviews/kstandarditem.cpp [deleted file]
src/kitemviews/kstandarditem.h [deleted file]
src/kitemviews/kstandarditemlistgroupheader.cpp
src/kitemviews/kstandarditemlistgroupheader.h
src/kitemviews/kstandarditemlistview.cpp
src/kitemviews/kstandarditemlistwidget.cpp
src/kitemviews/kstandarditemlistwidget.h
src/kitemviews/kstandarditemmodel.cpp [deleted file]
src/kitemviews/kstandarditemmodel.h [deleted file]
src/kitemviews/private/kbaloorolesprovider.cpp
src/kitemviews/private/kitemlistheaderwidget.cpp
src/kitemviews/private/kitemlistheaderwidget.h
src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp
src/kitemviews/private/kitemlistsizehintresolver.cpp
src/kitemviews/private/kitemlistsizehintresolver.h
src/kitemviews/private/kitemlistviewlayouter.cpp
src/main.cpp
src/org.kde.dolphin.appdata.xml
src/org.kde.dolphin.desktop
src/panels/folders/folderspanel.cpp
src/panels/folders/folderspanel.h
src/panels/information/informationpanelcontent.cpp
src/panels/information/informationpanelcontent.h
src/panels/places/dolphin_placespanelsettings.kcfg
src/panels/places/placesitem.cpp [deleted file]
src/panels/places/placesitem.h [deleted file]
src/panels/places/placesitemlistgroupheader.cpp [deleted file]
src/panels/places/placesitemlistgroupheader.h [deleted file]
src/panels/places/placesitemlistwidget.cpp [deleted file]
src/panels/places/placesitemlistwidget.h [deleted file]
src/panels/places/placesitemmodel.cpp [deleted file]
src/panels/places/placesitemmodel.h [deleted file]
src/panels/places/placesitemsignalhandler.cpp [deleted file]
src/panels/places/placesitemsignalhandler.h [deleted file]
src/panels/places/placespanel.cpp
src/panels/places/placespanel.h
src/panels/places/placesview.cpp [deleted file]
src/panels/places/placesview.h [deleted file]
src/panels/terminal/terminalpanel.cpp
src/panels/terminal/terminalpanel.h
src/search/dolphinfacetswidget.cpp
src/search/dolphinsearchbox.cpp
src/settings/contextmenu/contextmenusettingspage.cpp
src/settings/contextmenu/servicemenu.knsrc
src/settings/contextmenu/servicemenuinstaller/CMakeLists.txt
src/settings/dolphin_detailsmodesettings.kcfg
src/settings/dolphin_iconsmodesettings.kcfg
src/settings/dolphinsettingsdialog.cpp
src/settings/general/configurepreviewplugindialog.cpp
src/settings/general/previewssettingspage.cpp
src/settings/trash/trashsettingspage.cpp
src/settings/viewmodes/dolphinfontrequester.cpp
src/settings/viewmodes/viewsettingstab.cpp
src/settings/viewpropertiesdialog.cpp
src/tests/CMakeLists.txt
src/tests/kfileitemmodeltest.cpp
src/tests/kstandarditemmodeltest.cpp [deleted file]
src/tests/placesitemmodeltest.cpp [deleted file]
src/tests/testdir.cpp
src/trash/dolphintrash.cpp
src/views/dolphinitemlistview.cpp
src/views/dolphinremoteencoding.cpp
src/views/dolphinview.cpp
src/views/dolphinview.h
src/views/dolphinviewactionhandler.cpp
src/views/tooltips/dolphinfilemetadatawidget.cpp
src/views/tooltips/tooltipmanager.cpp
src/views/versioncontrol/kversioncontrolplugin.h
src/views/versioncontrol/versioncontrolobserver.cpp
src/views/versioncontrol/versioncontrolobserver.h

index 5dd9a93dc51cfadacb22071d28522d021f14570c..84fe503fa4f1aacf1faf69490a064b6a722776f4 100644 (file)
@@ -1,3 +1,3 @@
 include:
-  - https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-before.yml
-  - https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-applications-linux.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
+  - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
index bcf24537fe16216c34753ef02d1b50141b15bb13..3f3b7f8bc423ad2ed63c2106e5205408d4d6704e 100644 (file)
@@ -26,6 +26,6 @@ Dependencies:
     'frameworks/baloo': '@stable'
     'frameworks/kwindowsystem': '@stable'
     'frameworks/kfilemetadata': '@stable'
-    'libraries/baloo-widgets': '@stable'
+    'libraries/baloo-widgets': '@same'
     'libraries/kuserfeedback': '@stable'
     'libraries/phonon': '@stable'
index 7be5700fc3da86fbd3e580954cd25c5be818c5ec..1a346b8f2ed6af9f323d36b5cc581ffa41ab7b3e 100644 (file)
@@ -1,22 +1,23 @@
 cmake_minimum_required(VERSION 3.16)
 
 # KDE Application Version, managed by release script
-set (RELEASE_SERVICE_VERSION_MAJOR "21")
-set (RELEASE_SERVICE_VERSION_MINOR "12")
-set (RELEASE_SERVICE_VERSION_MICRO "2")
+set (RELEASE_SERVICE_VERSION_MAJOR "22")
+set (RELEASE_SERVICE_VERSION_MINOR "03")
+set (RELEASE_SERVICE_VERSION_MICRO "70")
 set (RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
 project(Dolphin VERSION ${RELEASE_SERVICE_VERSION})
 
 set(QT_MIN_VERSION "5.15.0")
-set(KF5_MIN_VERSION "5.83.0")
-
-set(CMAKE_CXX_STANDARD 17)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(KF5_MIN_VERSION "5.91.0")
 
 # ECM setup
 find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED)
 set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
 
+include(KDEInstallDirs)
+include(KDECMakeSettings)
+include(KDECompilerSettings NO_POLICY_SCOPE)
+
 include(ECMSetupVersion)
 include(ECMGenerateHeaders)
 include(ECMGenerateDBusServiceFile)
@@ -24,10 +25,8 @@ include(ECMConfiguredInstall)
 include(CMakePackageConfigHelpers)
 include(GenerateExportHeader)
 include(FeatureSummary)
-include(KDEInstallDirs)
-include(KDECMakeSettings)
-include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
 include(ECMQtDeclareLoggingCategory)
+include(ECMDeprecationSettings)
 
 ecm_setup_version(${RELEASE_SERVICE_VERSION} VARIABLE_PREFIX DOLPHIN
                   VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/src/dolphin_version.h"
@@ -43,7 +42,7 @@ ecm_setup_version("5.0.0" VARIABLE_PREFIX DOLPHINPRIVATE
                   SOVERSION 5
 )
 
-find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
+find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
     Core
     Concurrent
     Widgets
@@ -90,7 +89,7 @@ set_package_properties(KF5Activities PROPERTIES DESCRIPTION "KActivities librari
                        PURPOSE "For tracking which folders are frequently accessed on a Plasma desktop"
                       )
 
-find_package(Phonon4Qt5 CONFIG REQUIRED)
+find_package(Phonon4Qt${QT_MAJOR_VERSION} CONFIG REQUIRED)
 
 find_package(PackageKitQt5)
 set_package_properties(PackageKitQt5
@@ -140,6 +139,12 @@ else()
     set(HAVE_TERMINAL TRUE)
 endif()
 
+ecm_set_disabled_deprecation_versions(SHOW_DEPRECATIONS
+    QT 5.15
+    KF 5.90
+    KSERVICE 5.89 # We use KServiceTypeTrader in a compat code path
+)
+
 add_subdirectory(src)
 add_subdirectory(doc)
 
@@ -170,15 +175,24 @@ install(FILES
     COMPONENT Devel
 )
 
-ecm_generate_dbus_service_file(
-   NAME org.freedesktop.FileManager1
-   EXECUTABLE "${KDE_INSTALL_FULL_BINDIR}/dolphin --daemon"
-   SYSTEMD_SERVICE plasma-dolphin.service
-   DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}
-   RENAME org.kde.dolphin.FileManager1.service
-)
+if(FLATPAK)
+    ecm_generate_dbus_service_file(
+        NAME org.freedesktop.FileManager1
+        EXECUTABLE "${KDE_INSTALL_FULL_BINDIR}/dolphin --daemon"
+        SYSTEMD_SERVICE plasma-dolphin.service
+        DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}
+    )
+else()
+    ecm_generate_dbus_service_file(
+        NAME org.freedesktop.FileManager1
+        EXECUTABLE "${KDE_INSTALL_FULL_BINDIR}/dolphin --daemon"
+        SYSTEMD_SERVICE plasma-dolphin.service
+        DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}
+        RENAME org.kde.dolphin.FileManager1.service
+    )
+endif()
 
-ecm_install_configured_files(INPUT plasma-dolphin.service.in DESTINATION ${SYSTEMD_USER_UNIT_INSTALL_DIR})
+ecm_install_configured_files(INPUT plasma-dolphin.service.in DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR})
 
 ecm_qt_install_logging_categories(
         EXPORT DOLPHIN
index f544f4c610ed4232cefbaa64595ee9d298c0908c..b6e3d42f60954025b58d83c214622b98bff1ffbe 100644 (file)
@@ -2,7 +2,7 @@
 
 include(CMakeFindDependencyMacro)
 
-find_dependency(Qt5Widgets)
+find_dependency(Qt@QT_MAJOR_VERSION@Widgets)
 find_dependency(KF5KIO)
 
 include("${CMAKE_CURRENT_LIST_DIR}/DolphinVcsTargets.cmake")
index b5f6a80b0b6f0a3fd417eda9ff280ed13c259268..e2e32460274c9ec3810a993d4a606161fa3a7bb8 100644 (file)
@@ -1,5 +1,5 @@
 macro (generate_and_install_dbus_interface main_project_target header_file output_xml_file)
-    qt5_generate_dbus_interface(
+    qt_generate_dbus_interface(
         ${header_file}
         ${output_xml_file}
     )
index cb99abaa93a73bc2567c416510a5c9bcc7429734..a3fc94aaceb0a1012224a6e4bfdd2b655eb83309 100644 (file)
@@ -70,8 +70,8 @@
 
 <legalnotice>&FDLNotice;</legalnotice>
 
-<date>2021-04-08</date>
-<releaseinfo>Applications 21.08</releaseinfo>
+<date>2022-01-17</date>
+<releaseinfo>KDE Gear 22.04</releaseinfo>
 
 <abstract>
 <para>
@@ -349,9 +349,10 @@ as a detailed list which contains the name, size and last modification time of
 each item. Additional columns can be added by clicking a column header with the
 &RMB;.
 </para>
-<para>In the context menu of the header line you can choose between custom or automatic 
-column width. Automatic width adjusts the width of all columns once to display the longest 
-item in the column completely, except for the <guilabel>Name</guilabel> column where 
+<para>In the context menu of the header line, you can add or remove the padding from the
+leading column by checking or unchecking the respective menu item and choose between a custom
+or automatic column width. Automatic width adjusts the width of all columns once to display the
+longest item in the column completely, except for the <guilabel>Name</guilabel> column where 
 the extension is replaced by <quote>...</quote> 
 </para>
 <para>The order of columns can be changed by drag and drop of column headers, except for
index b97a5d7c0018a873b8c92352d1baf277eff19edc..bb898b5a75f1029a0901488ea1a527d3c0d2791f 100644 (file)
@@ -31,7 +31,7 @@ generate_export_header(dolphinvcs BASE_NAME dolphinvcs)
 
 target_link_libraries(
     dolphinvcs PUBLIC
-    Qt5::Widgets
+    Qt${QT_MAJOR_VERSION}::Widgets
 )
 
 set_target_properties(dolphinvcs PROPERTIES
@@ -50,7 +50,7 @@ ecm_generate_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 views/versioncontrol/fileviewversioncontrolplugin.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPESDIR})
 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)
 
@@ -74,11 +74,9 @@ target_sources(dolphinprivate PRIVATE
     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
@@ -147,8 +145,8 @@ generate_export_header(dolphinprivate BASE_NAME dolphin)
 target_link_libraries(
     dolphinprivate PUBLIC
     dolphinvcs
-    Qt5::Concurrent
-    Qt5::Gui
+    Qt${QT_MAJOR_VERSION}::Concurrent
+    Qt${QT_MAJOR_VERSION}::Gui
     KF5::I18n
     KF5::IconThemes
     KF5::KIOCore
@@ -200,7 +198,7 @@ target_link_libraries(dolphinpart
 
 install(TARGETS dolphinpart DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/parts)
 
-install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dolphinpart.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dolphinpart.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR})
 
 ##########################################
 
@@ -223,12 +221,6 @@ target_sources(dolphinstatic PRIVATE
     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
@@ -299,10 +291,10 @@ kconfig_add_kcfg_files(dolphinstatic GENERATE_MOC
     settings/dolphin_generalsettings.kcfgc
 )
 
-qt5_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/dolphinmainwindow.h org.kde.DolphinMainWindow.xml)
-qt5_add_dbus_adaptor(dolphin_dbus_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.DolphinMainWindow.xml dolphinmainwindow.h DolphinMainWindow)
-qt5_add_dbus_interface(dolphin_dbus_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.DolphinMainWindow.xml dolphinmainwindowinterface)
-qt5_add_dbus_interface(dolphin_dbus_SRCS panels/terminal/org.kde.KIOFuse.VFS.xml kiofuse_interface)
+qt_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/dolphinmainwindow.h org.kde.DolphinMainWindow.xml)
+qt_add_dbus_adaptor(dolphin_dbus_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.DolphinMainWindow.xml dolphinmainwindow.h DolphinMainWindow)
+qt_add_dbus_interface(dolphin_dbus_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.DolphinMainWindow.xml dolphinmainwindowinterface)
+qt_add_dbus_interface(dolphin_dbus_SRCS panels/terminal/org.kde.KIOFuse.VFS.xml kiofuse_interface)
 
 target_sources(dolphinstatic PRIVATE
     ${dolphin_dbus_SRCS}
@@ -340,6 +332,10 @@ target_sources(dolphin PRIVATE
     main.cpp
 )
 
+if(FLATPAK)
+    target_compile_definitions(dolphin PRIVATE FLATPAK)
+endif()
+
 # 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_APPICON_SRCS ICONS ${ICONS_SRCS})
@@ -419,9 +415,9 @@ if(NOT WIN32)
     target_link_libraries(kcm_dolphingeneral dolphinprivate)
 
     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/kcmdolphingeneral.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} )
+    install( FILES settings/kcm/kcmdolphinviewmodes.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR} )
+    install( FILES settings/kcm/kcmdolphinnavigation.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR} )
+    install( FILES settings/kcm/kcmdolphingeneral.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR} )
 
     install(TARGETS kcm_dolphinviewmodes DESTINATION ${KDE_INSTALL_PLUGINDIR}/dolphin/kcms )
     install(TARGETS kcm_dolphinnavigation DESTINATION ${KDE_INSTALL_PLUGINDIR}/dolphin/kcms )
@@ -453,6 +449,5 @@ install( FILES settings/dolphin_directoryviewpropertysettings.kcfg
          DESTINATION ${KDE_INSTALL_KCFGDIR} )
 
 if(BUILD_TESTING)
-    find_package(Qt5Test ${QT_MIN_VERSION} CONFIG REQUIRED)
     add_subdirectory(tests)
 endif()
index 41f03aa1ad541efd4ceb2786ef32e0f32d53d663..340af6bd0223be8e0c68e81725cda0719cd62c4c 100644 (file)
@@ -13,8 +13,6 @@
 #include "dolphinplacesmodelsingleton.h"
 #include "dolphinremoveaction.h"
 #include "dolphinviewcontainer.h"
-#include "panels/places/placesitem.h"
-#include "panels/places/placesitemmodel.h"
 #include "trash/dolphintrash.h"
 #include "views/dolphinview.h"
 #include "views/viewmodecontroller.h"
@@ -197,7 +195,6 @@ void DolphinContextMenu::addDirectoryItemContextMenu()
     // set up 'Create New' menu
     DolphinNewFileMenu* newFileMenu = new DolphinNewFileMenu(m_mainWindow->actionCollection(), m_mainWindow);
     const DolphinView* view = m_mainWindow->activeViewContainer()->view();
-    newFileMenu->setViewShowsHiddenFiles(view->hiddenFilesShown());
     newFileMenu->checkUpToDate();
     newFileMenu->setPopupFiles(QList<QUrl>() << m_fileInfo.url());
     newFileMenu->setEnabled(selectedItemsProps.supportsWriting());
@@ -315,7 +312,6 @@ void DolphinContextMenu::openViewportContextMenu()
 
     // Set up and insert 'Create New' menu
     KNewFileMenu* newFileMenu = m_mainWindow->newFileMenu();
-    newFileMenu->setViewShowsHiddenFiles(view->hiddenFilesShown());
     newFileMenu->checkUpToDate();
     newFileMenu->setPopupFiles(QList<QUrl>() << m_baseUrl);
     addMenu(newFileMenu->menu());
@@ -477,7 +473,7 @@ KFileItem DolphinContextMenu::baseFileItem()
 void DolphinContextMenu::addOpenWithActions()
 {
     // insert 'Open With...' action or sub menu
-    m_fileItemActions->addOpenWithActionsTo(this, QStringLiteral("DesktopEntryName != '%1'").arg(qApp->desktopFileName()));
+    m_fileItemActions->insertOpenWithActionsTo(nullptr, this, QStringList{qApp->desktopFileName()});
 }
 
 void DolphinContextMenu::addCustomActions()
index fa105b15a62674b0ac710b98d4361932e55960c6..9b4ac64c9829d63e601914a12137754ec6584233 100644 (file)
 #include "dolphinnavigatorswidgetaction.h"
 #include "dolphinnewfilemenu.h"
 #include "dolphinrecenttabsmenu.h"
+#include "dolphinplacesmodelsingleton.h"
 #include "dolphinurlnavigatorscontroller.h"
 #include "dolphinviewcontainer.h"
 #include "dolphintabpage.h"
 #include "middleclickactioneventfilter.h"
 #include "panels/folders/folderspanel.h"
-#include "panels/places/placesitemmodel.h"
 #include "panels/places/placespanel.h"
 #include "panels/terminal/terminalpanel.h"
 #include "settings/dolphinsettingsdialog.h"
@@ -53,6 +53,7 @@
 #include <KProtocolInfo>
 #include <KProtocolManager>
 #include <KShell>
+#include <KShortcutsDialog>
 #include <KStandardAction>
 #include <KStartupInfo>
 #include <KSycoca>
@@ -64,7 +65,6 @@
 #include <KUrlNavigator>
 #include <KWindowSystem>
 #include <KXMLGUIFactory>
-#include <kxmlgui_version.h>
 
 #include <kio_version.h>
 
@@ -123,9 +123,7 @@ DolphinMainWindow::DolphinMainWindow() :
     setComponentName(QStringLiteral("dolphin"), QGuiApplication::applicationDisplayName());
     setObjectName(QStringLiteral("Dolphin#"));
 
-#if KXMLGUI_VERSION >= QT_VERSION_CHECK(5, 88, 0)
     setStateConfigGroup("State");
-#endif
 
     connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::errorMessage,
             this, &DolphinMainWindow::showErrorMessage);
@@ -133,7 +131,7 @@ DolphinMainWindow::DolphinMainWindow() :
     KIO::FileUndoManager* undoManager = KIO::FileUndoManager::self();
     undoManager->setUiInterface(new UndoUiInterface());
 
-    connect(undoManager, QOverload<bool>::of(&KIO::FileUndoManager::undoAvailable),
+    connect(undoManager, &KIO::FileUndoManager::undoAvailable,
             this, &DolphinMainWindow::slotUndoAvailable);
     connect(undoManager, &KIO::FileUndoManager::undoTextChanged,
             this, &DolphinMainWindow::slotUndoTextChanged);
@@ -173,7 +171,7 @@ DolphinMainWindow::DolphinMainWindow() :
 
     setupDockWidgets();
 
-    setupGUI(Keys | Save | Create | ToolBar);
+    setupGUI(Save | Create | ToolBar);
     stateChanged(QStringLiteral("new_file"));
 
     QClipboard* clipboard = QApplication::clipboard();
@@ -211,7 +209,7 @@ DolphinMainWindow::DolphinMainWindow() :
 
     setupWhatsThis();
 
-    connect(KSycoca::self(), QOverload<>::of(&KSycoca::databaseChanged), this, &DolphinMainWindow::updateOpenPreferredSearchToolAction);
+    connect(KSycoca::self(), &KSycoca::databaseChanged, this, &DolphinMainWindow::updateOpenPreferredSearchToolAction);
 
     QTimer::singleShot(0, this, &DolphinMainWindow::updateOpenPreferredSearchToolAction);
 
@@ -429,14 +427,13 @@ void DolphinMainWindow::addToPlaces()
         name = dirToAdd.name();
     }
     if (url.isValid()) {
-        PlacesItemModel model;
         QString icon;
         if (m_activeViewContainer->isSearchModeEnabled()) {
             icon = QStringLiteral("folder-saved-search-symbolic");
         } else {
             icon = KIO::iconNameForUrl(url);
         }
-        model.createPlacesItem(name, url, icon);
+        DolphinPlacesModelSingleton::instance().placesModel()->addPlace(name, url, icon);
     }
 }
 
@@ -445,6 +442,16 @@ void DolphinMainWindow::openNewTab(const QUrl& url)
     m_tabWidget->openNewTab(url, QUrl());
 }
 
+void DolphinMainWindow::openNewTabAndActivate(const QUrl &url)
+{
+    m_tabWidget->openNewActivatedTab(url, QUrl());
+}
+
+void DolphinMainWindow::openNewWindow(const QUrl &url)
+{
+    Dolphin::openNewWindow({url}, this);
+}
+
 void DolphinMainWindow::slotSplitViewChanged()
 {
     m_tabWidget->currentTabPage()->setSplitViewEnabled(GeneralSettings::splitView(), WithAnimation);
@@ -647,14 +654,12 @@ void DolphinMainWindow::readProperties(const KConfigGroup& group)
 
 void DolphinMainWindow::updateNewMenu()
 {
-    m_newFileMenu->setViewShowsHiddenFiles(activeViewContainer()->view()->hiddenFilesShown());
     m_newFileMenu->checkUpToDate();
     m_newFileMenu->setPopupFiles(QList<QUrl>() << activeViewContainer()->url());
 }
 
 void DolphinMainWindow::createDirectory()
 {
-    m_newFileMenu->setViewShowsHiddenFiles(activeViewContainer()->view()->hiddenFilesShown());
     m_newFileMenu->setPopupFiles(QList<QUrl>() << activeViewContainer()->url());
     m_newFileMenu->createDirectory();
 }
@@ -1172,6 +1177,16 @@ void DolphinMainWindow::openContextMenu(const QPoint& pos,
     }
 }
 
+QMenu *DolphinMainWindow::createPopupMenu()
+{
+    QMenu *menu = KXmlGuiWindow::createPopupMenu();
+
+    menu->addSeparator();
+    menu->addAction(actionCollection()->action(QStringLiteral("lock_panels")));
+
+    return menu;
+}
+
 void DolphinMainWindow::updateHamburgerMenu()
 {
     KActionCollection* ac = actionCollection();
@@ -1368,6 +1383,19 @@ void DolphinMainWindow::slotStorageTearDownExternallyRequested(const QString& mo
     }
 }
 
+void DolphinMainWindow::slotKeyBindings()
+{
+    KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this);
+    dialog.addCollection(actionCollection());
+    if (m_terminalPanel) {
+        KActionCollection *konsolePartActionCollection = m_terminalPanel->actionCollection();
+        if (konsolePartActionCollection) {
+            dialog.addCollection(konsolePartActionCollection, QStringLiteral("KonsolePart"));
+        }
+    }
+    dialog.configure();
+}
+
 void DolphinMainWindow::setViewsToHomeIfMountPathOpen(const QString& mountPath)
 {
     const QVector<DolphinViewContainer*> theViewContainers = viewContainers();
@@ -1480,7 +1508,7 @@ void DolphinMainWindow::setupActions()
 
     QAction* showFilterBar = actionCollection()->addAction(QStringLiteral("show_filter_bar"));
     showFilterBar->setText(i18nc("@action:inmenu Tools", "Filter..."));
-    showFilterBar->setToolTip(i18nc("@info:tooltip", "Toggle Filter Bar"));
+    showFilterBar->setToolTip(i18nc("@info:tooltip", "Show Filter Bar"));
     showFilterBar->setWhatsThis(xi18nc("@info:whatsthis", "This opens the "
         "<emphasis>Filter Bar</emphasis> at the bottom of the window.<nl/> "
         "There you can enter a text to filter the files and folders currently displayed. "
@@ -1705,6 +1733,7 @@ void DolphinMainWindow::setupActions()
             "contain mostly the same commands and configuration options."));
     connect(showMenuBar, &KToggleAction::triggered,                   // Fixes #286822
             this, &DolphinMainWindow::toggleShowMenuBar, Qt::QueuedConnection);
+    KStandardAction::keyBindings(this, &DolphinMainWindow::slotKeyBindings, actionCollection());
     KStandardAction::preferences(this, &DolphinMainWindow::editSettings, actionCollection());
 
     // setup 'Help' menu for the m_controlButton. The other one is set up in the base class.
@@ -1861,8 +1890,10 @@ void DolphinMainWindow::setupDockWidgets()
             foldersPanel, &FoldersPanel::setUrl);
     connect(foldersPanel, &FoldersPanel::folderActivated,
             this, &DolphinMainWindow::changeUrl);
-    connect(foldersPanel, &FoldersPanel::folderMiddleClicked,
+    connect(foldersPanel, &FoldersPanel::folderInNewTab,
             this, &DolphinMainWindow::openNewTab);
+    connect(foldersPanel, &FoldersPanel::folderInNewActiveTab,
+            this, &DolphinMainWindow::openNewTabAndActivate);
     connect(foldersPanel, &FoldersPanel::errorMessage,
             this, &DolphinMainWindow::showErrorMessage);
 
@@ -1944,8 +1975,13 @@ void DolphinMainWindow::setupDockWidgets()
     addDockWidget(Qt::LeftDockWidgetArea, placesDock);
     connect(m_placesPanel, &PlacesPanel::placeActivated,
             this, &DolphinMainWindow::slotPlaceActivated);
-    connect(m_placesPanel, &PlacesPanel::placeMiddleClicked,
+    connect(m_placesPanel, &PlacesPanel::tabRequested,
             this, &DolphinMainWindow::openNewTab);
+    connect(m_placesPanel, &PlacesPanel::activeTabRequested,
+            this, &DolphinMainWindow::openNewTabAndActivate);
+    connect(m_placesPanel, &PlacesPanel::newWindowRequested, this, [this](const QUrl &url) {
+        Dolphin::openNewWindow({url}, this);
+    });
     connect(m_placesPanel, &PlacesPanel::errorMessage,
             this, &DolphinMainWindow::showErrorMessage);
     connect(this, &DolphinMainWindow::urlChanged,
@@ -1968,14 +2004,9 @@ void DolphinMainWindow::setupDockWidgets()
         "appear semi-transparent unless you uncheck their hide property."));
 
     connect(actionShowAllPlaces, &QAction::triggered, this, [actionShowAllPlaces, this](bool checked){
-        actionShowAllPlaces->setIcon(QIcon::fromTheme(checked ? QStringLiteral("view-visible") : QStringLiteral("view-hidden")));
-        m_placesPanel->showHiddenEntries(checked);
+        m_placesPanel->setShowAll(checked);
     });
-
-    connect(m_placesPanel, &PlacesPanel::showHiddenEntriesChanged, this, [actionShowAllPlaces] (bool checked){
-        actionShowAllPlaces->setChecked(checked);
-        actionShowAllPlaces->setIcon(QIcon::fromTheme(checked ? QStringLiteral("view-visible") : QStringLiteral("view-hidden")));
-   });
+    connect(m_placesPanel, &PlacesPanel::allPlacesShownChanged, actionShowAllPlaces, &QAction::setChecked);
 
     actionCollection()->action(QStringLiteral("show_places_panel"))
         ->setWhatsThis(xi18nc("@info:whatsthis", "<para>This toggles the "
@@ -2013,7 +2044,7 @@ void DolphinMainWindow::setupDockWidgets()
     panelsMenu->addAction(lockLayoutAction);
 
     connect(panelsMenu->menu(), &QMenu::aboutToShow, this, [actionShowAllPlaces, this]{
-        actionShowAllPlaces->setEnabled(m_placesPanel->hiddenListCount());
+        actionShowAllPlaces->setEnabled(DolphinPlacesModelSingleton::instance().placesModel()->hiddenCount());
     });
 }
 
@@ -2136,6 +2167,10 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer* container)
             this, &DolphinMainWindow::updateSearchAction);
     connect(container, &DolphinViewContainer::captionChanged,
             this, &DolphinMainWindow::updateWindowTitle);
+    connect(container, &DolphinViewContainer::tabRequested,
+            this, &DolphinMainWindow::openNewTab);
+    connect(container, &DolphinViewContainer::activeTabRequested,
+            this, &DolphinMainWindow::openNewTabAndActivate);
 
     const QAction* toggleSearchAction = actionCollection()->action(QStringLiteral("toggle_search"));
     connect(toggleSearchAction, &QAction::triggered, container, &DolphinViewContainer::setSearchModeEnabled);
@@ -2149,6 +2184,10 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer* container)
             this, &DolphinMainWindow::fileItemsChanged);
     connect(view, &DolphinView::tabRequested,
             this, &DolphinMainWindow::openNewTab);
+    connect(view, &DolphinView::activeTabRequested,
+            this, &DolphinMainWindow::openNewTabAndActivate);
+    connect(view, &DolphinView::windowRequested,
+            this, &DolphinMainWindow::openNewWindow);
     connect(view, &DolphinView::requestContextMenu,
             this, &DolphinMainWindow::openContextMenu);
     connect(view, &DolphinView::directoryLoadingStarted,
@@ -2183,6 +2222,10 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer* container)
             this, &DolphinMainWindow::slotEditableStateChanged);
     connect(navigator, &KUrlNavigator::tabRequested,
             this, &DolphinMainWindow::openNewTab);
+    connect(navigator, &KUrlNavigator::activeTabRequested,
+            this, &DolphinMainWindow::openNewTabAndActivate);
+    connect(navigator, &KUrlNavigator::newWindowRequested,
+            this, &DolphinMainWindow::openNewWindow);
 
 }
 
@@ -2245,6 +2288,7 @@ void DolphinMainWindow::createPanelAction(const QIcon& icon,
     panelAction->setChecked(dockAction->isChecked());
     panelAction->setText(dockAction->text());
     panelAction->setIcon(icon);
+    dockAction->setIcon(icon);
     actionCollection()->setDefaultShortcut(panelAction, shortcut);
 
     connect(panelAction, &QAction::triggered, dockAction, &QAction::trigger);
index 46515cc8bee4000411f58e7b9fb70fbf5b286eb8..1a3ec4efb7a7e8d3b9e112df506fc48c7b93abef 100644 (file)
@@ -95,6 +95,12 @@ public:
      */
     KNewFileMenu* newFileMenu() const;
 
+    /**
+     * Augments Qt's build-in QMainWindow context menu to add
+     * Dolphin-specific actions, such as "unlock panels".
+     */
+    QMenu *createPopupMenu() override;
+
     /**
      * Switch the window's view containers' locations to display the home path
      * for any which are currently displaying a location corresponding to or
@@ -171,6 +177,16 @@ public Q_SLOTS:
      */
     void openNewTab(const QUrl& url);
 
+    /**
+     * Opens a new tab  showing the URL \a url and activate it.
+     */
+    void openNewTabAndActivate(const QUrl &url);
+
+    /**
+     * Opens a new window showing the URL \a url.
+     */
+    void openNewWindow(const QUrl &url);
+
     /** @see GeneralSettings::splitViewChanged() */
     void slotSplitViewChanged();
 
@@ -566,6 +582,12 @@ private Q_SLOTS:
       * to go to.
       */
     void slotGoForward(QAction* action);
+
+    /**
+     * Is called when configuring the keyboard shortcuts
+     */
+    void slotKeyBindings();
+
 private:
     /**
      * Sets up the various menus and actions and connects them.
index 3f50728e93778c7e74eb3346b3cc31c26d424e5b..d33482201aed6adb56fc590fb579e23eb7c0eefb 100644 (file)
@@ -57,7 +57,7 @@ public:
      * This method should preferably only be called when:
      * - Split view is activated in the active tab
      * OR
-     * - A switch to a tab that is already in split view mode is occuring
+     * - A switch to a tab that is already in split view mode is occurring
      */
     void createSecondaryUrlNavigator();
 
@@ -92,7 +92,7 @@ protected:
      * this method always returns the same widget and reparents it.
      * You normally don't have to use this method directly because
      * QWidgetAction::requestWidget() is used to obtain the navigatorsWidget
-     * and to steal it from whereever it was prior.
+     * and to steal it from wherever it was prior.
      * @param parent the new parent of the navigatorsWidget.
      */
     QWidget *createWidget(QWidget *parent) override;
index 8d528f418a9b817826011bcfdafaa85dbb2b5343..0595087784a33ea37b75623688eb34c91567008c 100644 (file)
@@ -68,7 +68,7 @@ DolphinPart::DolphinPart(QWidget* parentWidget, QObject* parent,
     connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::errorMessage,
             this, &DolphinPart::slotErrorMessage);
 
-    connect(m_view, &DolphinView::directoryLoadingCompleted, this, QOverload<>::of(&KParts::ReadOnlyPart::completed));
+    connect(m_view, &DolphinView::directoryLoadingCompleted, this, &KParts::ReadOnlyPart::completed);
     connect(m_view, &DolphinView::directoryLoadingCompleted, this, &DolphinPart::updatePasteAction);
     connect(m_view, &DolphinView::directoryLoadingProgress, this, &DolphinPart::updateProgress);
     connect(m_view, &DolphinView::errorMessage, this, &DolphinPart::slotErrorMessage);
@@ -94,7 +94,7 @@ DolphinPart::DolphinPart(QWidget* parentWidget, QObject* parent,
     connect(m_view, &DolphinView::requestContextMenu,
             this, &DolphinPart::slotOpenContextMenu);
     connect(m_view, &DolphinView::selectionChanged,
-            m_extension, QOverload<const KFileItemList&>::of(&KParts::BrowserExtension::selectionInfo));
+            m_extension, &KParts::BrowserExtension::selectionInfo);
     connect(m_view, &DolphinView::selectionChanged,
             this, &DolphinPart::slotSelectionChanged);
     connect(m_view, &DolphinView::requestItemInfo,
@@ -146,8 +146,6 @@ DolphinPart::DolphinPart(QWidget* parentWidget, QObject* parent,
 
     // TODO there was a "always open a new window" (when clicking on a directory) setting in konqueror
     // (sort of spacial navigation)
-
-    loadPlugins(this, this, componentName());
 }
 
 DolphinPart::~DolphinPart()
@@ -593,7 +591,6 @@ void DolphinPart::updateNewMenu()
 {
     // As requested by KNewFileMenu :
     m_newFileMenu->checkUpToDate();
-    m_newFileMenu->setViewShowsHiddenFiles(m_view->hiddenFilesShown());
     // And set the files that the menu apply on :
     m_newFileMenu->setPopupFiles(QList<QUrl>() << url());
 }
@@ -610,7 +607,6 @@ void DolphinPart::updateProgress(int percent)
 
 void DolphinPart::createDirectory()
 {
-    m_newFileMenu->setViewShowsHiddenFiles(m_view->hiddenFilesShown());
     m_newFileMenu->setPopupFiles(QList<QUrl>() << url());
     m_newFileMenu->createDirectory();
 }
index 30ec1b9b634283610b4e55a863dfc6180a6be7e9..754070c9298bc6f610f5119a8b3d5dfd1a7dd895 100644 (file)
@@ -5,12 +5,61 @@
  */
 
 #include "dolphinplacesmodelsingleton.h"
+#include "trash/dolphintrash.h"
 
 #include <KAboutData>
 #include <KFilePlacesModel>
 
+#include <QIcon>
+
+DolphinPlacesModel::DolphinPlacesModel(const QString &alternativeApplicationName, QObject *parent)
+    : KFilePlacesModel(alternativeApplicationName, parent)
+{
+    connect(&Trash::instance(), &Trash::emptinessChanged, this, &DolphinPlacesModel::slotTrashEmptinessChanged);
+}
+
+DolphinPlacesModel::~DolphinPlacesModel() = default;
+
+QVariant DolphinPlacesModel::data(const QModelIndex &index, int role) const
+{
+    if (role == Qt::DecorationRole) {
+        if (isTrash(index)) {
+            if (m_isEmpty) {
+                return QIcon::fromTheme(QStringLiteral("user-trash"));
+            } else {
+                return QIcon::fromTheme(QStringLiteral("user-trash-full"));
+            }
+        }
+    }
+
+    return KFilePlacesModel::data(index, role);
+}
+
+void DolphinPlacesModel::slotTrashEmptinessChanged(bool isEmpty)
+{
+    if (m_isEmpty == isEmpty) {
+        return;
+    }
+
+    // NOTE Trash::isEmpty() reads the config file whereas emptinessChanged is
+    // hooked up to whether a dirlister in trash:/ has any files and they disagree...
+    m_isEmpty = isEmpty;
+
+    for (int i = 0; i < rowCount(); ++i) {
+        const QModelIndex index = this->index(i, 0);
+        if (isTrash(index)) {
+            Q_EMIT dataChanged(index, index, {Qt::DecorationRole});
+        }
+    }
+}
+
+bool DolphinPlacesModel::isTrash(const QModelIndex &index) const
+{
+    return url(index) == QUrl(QStringLiteral("trash:/"));
+}
+
 DolphinPlacesModelSingleton::DolphinPlacesModelSingleton()
-    : m_placesModel(new KFilePlacesModel(KAboutData::applicationData().componentName() + applicationNameSuffix()))
+    : m_placesModel(new DolphinPlacesModel(KAboutData::applicationData().componentName() + applicationNameSuffix()))
 {
 
 }
index 645947aaa0c190cb189cd75dbb5e67a0c69dd46b..7efe6e093cc6ff08e7bb8cd31b6cd1b45d172a44 100644 (file)
 #include <QString>
 #include <QScopedPointer>
 
-class KFilePlacesModel;
+#include <KFilePlacesModel>
+
+/**
+ * @brief Dolphin's special-cased KFilePlacesModel
+ *
+ * It returns the trash's icon based on whether
+ * it is full or not.
+ */
+class DolphinPlacesModel : public KFilePlacesModel
+{
+    Q_OBJECT
+
+public:
+    explicit DolphinPlacesModel(const QString &alternativeApplicationName, QObject *parent = nullptr);
+    ~DolphinPlacesModel() override;
+
+protected:
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+private Q_SLOTS:
+    void slotTrashEmptinessChanged(bool isEmpty);
+
+private:
+    bool isTrash(const QModelIndex &index) const;
+
+    bool m_isEmpty = false;
+};
 
 /**
  * @brief Provides a global KFilePlacesModel instance.
index 67a61b0315810ebb2c9d204f5b2df2c74c436827..4c1d9e44ae3a954185531e3111ba636d2fd8b562 100644 (file)
@@ -36,7 +36,13 @@ void DolphinTabBar::dragEnterEvent(QDragEnterEvent* event)
     const int index = tabAt(event->pos());
 
     if (mimeData->hasUrls()) {
-        event->acceptProposedAction();
+        if (index >= 0) {
+            event->acceptProposedAction();
+        } else {
+            event->setDropAction(Qt::IgnoreAction);
+            // Still need to accept it to receive dragMoveEvent
+            event->accept();
+        }
         updateAutoActivationTimer(index);
     }
 
@@ -56,6 +62,11 @@ void DolphinTabBar::dragMoveEvent(QDragMoveEvent* event)
     const int index = tabAt(event->pos());
 
     if (mimeData->hasUrls()) {
+        if (index >= 0) {
+            event->acceptProposedAction();
+        } else {
+            event->setDropAction(Qt::IgnoreAction);
+        }
         updateAutoActivationTimer(index);
     }
 
index f1a784eb7427a2d3264b908616951153006b89d4..a8c1ba31104607cb12060cbf0932b58c930ede94 100644 (file)
@@ -46,7 +46,7 @@ public:
      *
      * @param enabled      If true, creates a secondary viewContainer in this tab.
      *                     Otherwise deletes it.
-     * @param animated     Decides wether the effects of this method call should
+     * @param animated     Decides whether the effects of this method call should
      *                     happen instantly or be transitioned to smoothly.
      * @param secondaryUrl If \p enabled is true, the new viewContainer will be opened at this
      *                     parameter. The default value will set the Url of the new viewContainer
index 4f6802725fc38a84b299658e0e3f3aa325e82322..fec15f4811e4743214efdb95d33381bc90f34d4b 100644 (file)
@@ -41,7 +41,7 @@ public Q_SLOTS:
 
 private:
     /**
-     * @return wether the places selector of DolphinUrlNavigators should be visible.
+     * @return whether the places selector of DolphinUrlNavigators should be visible.
      */
     static bool placesSelectorVisible();
 
index 70e473601fb9543e976f712f1d4bfb9d53a6e4f9..eec2d4c86f90e980af45a9eee31b9a55f6393cc4 100644 (file)
@@ -32,6 +32,7 @@
 #include <KUrlComboBox>
 
 #include <QDropEvent>
+#include <QGuiApplication>
 #include <QLoggingCategory>
 #include <QMimeData>
 #include <QTimer>
@@ -151,6 +152,10 @@ DolphinViewContainer::DolphinViewContainer(const QUrl& url, QWidget* parent) :
             this, &DolphinViewContainer::slotUrlIsFileError);
     connect(m_view, &DolphinView::activated,
             this, &DolphinViewContainer::activate);
+    connect(m_view, &DolphinView::hiddenFilesShownChanged,
+            this, &DolphinViewContainer::slotHiddenFilesShownChanged);
+    connect(m_view, &DolphinView::sortHiddenLastChanged,
+            this, &DolphinViewContainer::slotSortHiddenLastChanged);
 
     // Initialize status bar
     m_statusBar = new DolphinStatusBar(this);
@@ -309,6 +314,8 @@ void DolphinViewContainer::connectUrlNavigator(DolphinUrlNavigator *urlNavigator
     Q_CHECK_PTR(m_view);
 
     urlNavigator->setLocationUrl(m_view->url());
+    urlNavigator->setShowHiddenFolders(m_view->hiddenFilesShown());
+    urlNavigator->setSortHiddenFoldersLast(m_view->sortHiddenLast());
     if (m_urlNavigatorVisualState) {
         urlNavigator->setVisualState(*m_urlNavigatorVisualState.get());
         m_urlNavigatorVisualState.reset();
@@ -636,7 +643,7 @@ void DolphinViewContainer::slotUrlIsFileError(const QUrl& url)
     }
 }
 
-void DolphinViewContainer::slotItemActivated(const KFileItemitem)
+void DolphinViewContainer::slotItemActivated(const KFileItem &item)
 {
     // It is possible to activate items on inactive views by
     // drag & drop operations. Assure that activating an item always
@@ -645,13 +652,24 @@ void DolphinViewContainer::slotItemActivated(const KFileItem& item)
 
     const QUrl& url = DolphinView::openItemAsFolderUrl(item, GeneralSettings::browseThroughArchives());
     if (!url.isEmpty()) {
-        setUrl(url);
+        const auto modifiers = QGuiApplication::keyboardModifiers();
+        // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
+        if (modifiers & Qt::ControlModifier && modifiers & Qt::ShiftModifier) {
+            Q_EMIT activeTabRequested(url);
+        } else if (modifiers & Qt::ControlModifier) {
+            Q_EMIT tabRequested(url);
+        } else if (modifiers & Qt::ShiftModifier) {
+            Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(url)}, this);
+        } else {
+            setUrl(url);
+        }
         return;
     }
 
     KIO::OpenUrlJob *job = new KIO::OpenUrlJob(item.targetUrl(), item.mimetype());
-    job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
+    job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoWarningHandlingEnabled, this));
     job->setShowOpenOrExecuteDialog(true);
+    connect(job, &KIO::OpenUrlJob::finished, this, &DolphinViewContainer::slotOpenUrlFinished);
     job->start();
 }
 
@@ -660,7 +678,7 @@ void DolphinViewContainer::slotItemsActivated(const KFileItemList& items)
     Q_ASSERT(items.count() >= 2);
 
     KFileItemActions fileItemActions(this);
-    fileItemActions.runPreferredApplications(items, QString());
+    fileItemActions.runPreferredApplications(items);
 }
 
 void DolphinViewContainer::showItemInfo(const KFileItem& item)
@@ -809,6 +827,27 @@ void DolphinViewContainer::slotPlacesModelChanged()
     }
 }
 
+void DolphinViewContainer::slotHiddenFilesShownChanged(bool showHiddenFiles)
+{
+    if (m_urlNavigatorConnected) {
+        m_urlNavigatorConnected->setShowHiddenFolders(showHiddenFiles);
+    }
+}
+
+void DolphinViewContainer::slotSortHiddenLastChanged(bool hiddenLast)
+{
+    if (m_urlNavigatorConnected) {
+        m_urlNavigatorConnected->setSortHiddenFoldersLast(hiddenLast);
+    }
+}
+
+void DolphinViewContainer::slotOpenUrlFinished(KJob *job)
+{
+    if (job->error() && job->error() != KIO::ERR_USER_CANCELED) {
+        showErrorMessage(job->errorString());
+    }
+}
+
 bool DolphinViewContainer::isSearchUrl(const QUrl& url) const
 {
     return url.scheme().contains(QLatin1String("search"));
index 304c9958d9646dc77b0888275d6286b6626f81e8..f78f85e55c5c75746a39565d42f2d6468f7a98a0 100644 (file)
@@ -126,7 +126,7 @@ public:
     void connectUrlNavigator(DolphinUrlNavigator *urlNavigator);
 
     /**
-     * Disconnects the navigator that is currently controling the view.
+     * Disconnects the navigator that is currently controlling the view.
      * This method completely reverses connectUrlNavigator().
      */
     void disconnectUrlNavigator();
@@ -229,6 +229,16 @@ Q_SIGNALS:
      */
     void captionChanged();
 
+    /**
+     * Is emitted if a new tab should be opened in the background for the URL \a url.
+     */
+    void tabRequested(const QUrl &url);
+
+    /**
+     * Is emitted if a new tab should be opened for the URL \a url and set as active.
+     */
+    void activeTabRequested(const QUrl &url);
+
 private Q_SLOTS:
     /**
      * Updates the number of items (= number of files + number of
@@ -281,7 +291,7 @@ private Q_SLOTS:
      * directory is opened in the view. If the item is a file, the file
      * gets started by the corresponding application.
      */
-    void slotItemActivated(const KFileItemitem);
+    void slotItemActivated(const KFileItem &item);
 
     /**
      * Handles activation of multiple files. The files get started by
@@ -361,6 +371,11 @@ private Q_SLOTS:
      */
     void slotPlacesModelChanged();
 
+    void slotHiddenFilesShownChanged(bool showHiddenFiles);
+    void slotSortHiddenLastChanged(bool hiddenLast);
+
+    void slotOpenUrlFinished(KJob* job);
+
 private:
     /**
      * @return True if the URL protocol is a search URL (e. g. baloosearch:// or filenamesearch://).
index 587603ab32d291f88f382bc3b43e3d8e6e2f7f55..a62b75824c268ec4442d7ef61692af569aac03eb 100644 (file)
@@ -94,6 +94,11 @@ QString KFileItemListWidgetInformant::roleText(const QByteArray& role,
         if (dateTime.isValid()) {
             text = formatDate(dateTime);
         }
+    } else if (role == "dimensions") {
+        const auto dimensions = roleValue.toSize();
+        if (dimensions.isValid()) {
+            text = i18nc("width × height", "%1 × %2", dimensions.width(), dimensions.height());
+        }
     } else {
         text = KStandardItemListWidgetInformant::roleText(role, values);
     }
@@ -109,7 +114,6 @@ QFont KFileItemListWidgetInformant::customizedFontForLinks(const QFont& baseFont
     return font;
 }
 
-
 KFileItemListWidget::KFileItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) :
     KStandardItemListWidget(informant, parent)
 {
index d1235510061bf430d95e9095313433df9fe6be57..23afb087a31a29d09730529f8f7cbd50db37b694 100644 (file)
@@ -16,6 +16,7 @@
 #include <KDirLister>
 #include <KIO/Job>
 #include <KLocalizedString>
+#include <KLazyLocalizedString>
 #include <KUrlMimeData>
 
 #include <QElapsedTimer>
@@ -25,6 +26,8 @@
 #include <QWidget>
 #include <QRecursiveMutex>
 #include <QIcon>
+#include <algorithm>
+#include <klazylocalizedstring.h>
 
 Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex)
 
@@ -64,15 +67,15 @@ KFileItemModel::KFileItemModel(QObject* parent) :
     }
 
     connect(m_dirLister, &KCoreDirLister::started, this, &KFileItemModel::directoryLoadingStarted);
-    connect(m_dirLister, QOverload<>::of(&KCoreDirLister::canceled), this, &KFileItemModel::slotCanceled);
+    connect(m_dirLister, &KCoreDirLister::canceled, this, &KFileItemModel::slotCanceled);
     connect(m_dirLister, &KCoreDirLister::itemsAdded, this, &KFileItemModel::slotItemsAdded);
     connect(m_dirLister, &KCoreDirLister::itemsDeleted, this, &KFileItemModel::slotItemsDeleted);
     connect(m_dirLister, &KCoreDirLister::refreshItems, this, &KFileItemModel::slotRefreshItems);
-    connect(m_dirLister, QOverload<>::of(&KCoreDirLister::clear), this, &KFileItemModel::slotClear);
+    connect(m_dirLister, &KCoreDirLister::clear, this, &KFileItemModel::slotClear);
     connect(m_dirLister, &KCoreDirLister::infoMessage, this, &KFileItemModel::infoMessage);
     connect(m_dirLister, &KCoreDirLister::jobError, this, &KFileItemModel::slotListerError);
     connect(m_dirLister, &KCoreDirLister::percent, this, &KFileItemModel::directoryLoadingProgress);
-    connect(m_dirLister, QOverload<const QUrl&, const QUrl&>::of(&KCoreDirLister::redirection), this, &KFileItemModel::directoryRedirection);
+    connect(m_dirLister, &KCoreDirLister::redirection, this, &KFileItemModel::directoryRedirection);
     connect(m_dirLister, &KCoreDirLister::listingDirCompleted, this, &KFileItemModel::slotCompleted);
 
     // Apply default roles that should be determined
@@ -149,6 +152,18 @@ QHash<QByteArray, QVariant> KFileItemModel::data(int index) const
         ItemData* data = m_itemData.at(index);
         if (data->values.isEmpty()) {
             data->values = retrieveData(data->item, data->parent);
+        } else if (data->values.count() <= 2 && data->values.value("isExpanded").toBool()) {
+            // Special case dealt by slotRefreshItems(), avoid losing the "isExpanded" and "expandedParentsCount" state when refreshing
+            // slotRefreshItems() makes sure folders keep the "isExpanded" and "expandedParentsCount" while clearing the remaining values
+            // so this special request of different behavior can be identified here.
+            bool hasExpandedParentsCount = false;
+            const int expandedParentsCount = data->values.value("expandedParentsCount").toInt(&hasExpandedParentsCount);
+
+            data->values = retrieveData(data->item, data->parent);
+            data->values.insert("isExpanded", true);
+            if (hasExpandedParentsCount) {
+                data->values.insert("expandedParentsCount", expandedParentsCount);
+            }
         }
 
         return data->values;
@@ -312,10 +327,10 @@ QString KFileItemModel::roleDescription(const QByteArray& role) const
         int count = 0;
         const RoleInfoMap* map = rolesInfoMap(count);
         for (int i = 0; i < count; ++i) {
-            if (!map[i].roleTranslation) {
+            if (map[i].roleTranslation.isEmpty()) {
                 continue;
             }
-            description.insert(map[i].role, i18nc(map[i].roleTranslationContext, map[i].roleTranslation));
+            description.insert(map[i].role, map[i].roleTranslation.toString());
         }
     }
 
@@ -712,7 +727,7 @@ void KFileItemModel::applyFilters()
         ItemData *itemData = m_itemData.at(index);
 
         if (m_filter.matches(itemData->item)
-            || (itemShownBelow && itemShownBelow->parent == itemData && itemData->values.value("isExpanded").toBool())) {
+            || (itemShownBelow && itemShownBelow->parent == itemData)) {
             // We could've entered here for two reasons:
             // 1. This item passes the filter itself
             // 2. This is an expanded folder that doesn't pass the filter but sees a filter-passing child just below
@@ -815,9 +830,9 @@ QList<KFileItemModel::RoleInfo> KFileItemModel::rolesInformation()
             if (map[i].roleType != NoRole) {
                 RoleInfo info;
                 info.role = map[i].role;
-                info.translation = i18nc(map[i].roleTranslationContext, map[i].roleTranslation);
-                if (map[i].groupTranslation) {
-                    info.group = i18nc(map[i].groupTranslationContext, map[i].groupTranslation);
+                info.translation = map[i].roleTranslation.toString();
+                if (!map[i].groupTranslation.isEmpty()) {
+                    info.group = map[i].groupTranslation.toString();
                 } else {
                     // For top level roles, groupTranslation is 0. We must make sure that
                     // info.group is an empty string then because the code that generates
@@ -1010,12 +1025,7 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
 {
     Q_ASSERT(!items.isEmpty());
 
-    QUrl parentUrl;
-    if (m_expandedDirs.contains(directoryUrl)) {
-        parentUrl = m_expandedDirs.value(directoryUrl);
-    } else {
-        parentUrl = directoryUrl.adjusted(QUrl::StripTrailingSlash);
-    }
+    const QUrl parentUrl = m_expandedDirs.value(directoryUrl, directoryUrl.adjusted(QUrl::StripTrailingSlash));
 
     if (m_requestRole[ExpandedParentsCountRole]) {
         // If the expanding of items is enabled, the call
@@ -1049,16 +1059,28 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
     if (!m_filter.hasSetFilters()) {
         m_pendingItemsToInsert.append(itemDataList);
     } else {
+        QSet<ItemData *> parentsToEnsureVisible;
+
         // The name or type filter is active. Hide filtered items
         // before inserting them into the model and remember
         // the filtered items in m_filteredItems.
         for (ItemData* itemData : itemDataList) {
             if (m_filter.matches(itemData->item)) {
                 m_pendingItemsToInsert.append(itemData);
+                if (itemData->parent) {
+                    parentsToEnsureVisible.insert(itemData->parent);
+                }
             } else {
                 m_filteredItems.insert(itemData->item, itemData);
             }
         }
+
+        // Entire parental chains must be shown
+        for (ItemData *parent : parentsToEnsureVisible) {
+            for (; parent && m_filteredItems.remove(parent->item); parent = parent->parent) {
+                m_pendingItemsToInsert.append(parent);
+            }
+        }
     }
 
     if (!m_maximumUpdateIntervalTimer->isActive()) {
@@ -1070,6 +1092,42 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis
     Q_EMIT fileItemsChanged({KFileItem(directoryUrl)});
 }
 
+int KFileItemModel::filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible)
+{
+    int filteredParentsCount = 0;
+    // The childless parents not yet removed will always be right above the start of a removed range.
+    // We iterate backwards to ensure the deepest folders are processed before their parents
+    for (int i = removedItemRanges.size() - 1; i >= 0; i--) {
+        KItemRange itemRange = removedItemRanges.at(i);
+        const ItemData *const firstInRange = m_itemData.at(itemRange.index);
+        ItemData *itemAbove = itemRange.index - 1 >= 0 ? m_itemData.at(itemRange.index - 1) : nullptr;
+        const ItemData *const itemBelow = itemRange.index + itemRange.count < m_itemData.count() ? m_itemData.at(itemRange.index + itemRange.count) : nullptr;
+
+        if (itemAbove && firstInRange->parent == itemAbove && !m_filter.matches(itemAbove->item) && (!itemBelow || itemBelow->parent != itemAbove)
+            && !parentsToEnsureVisible.contains(itemAbove)) {
+            // The item above exists, is the parent, doesn't pass the filter, does not belong to parentsToEnsureVisible
+            // and this deleted range covers all of its descendents, so none will be left.
+            m_filteredItems.insert(itemAbove->item, itemAbove);
+            // This range's starting index will be extended to include the parent above:
+            --itemRange.index;
+            ++itemRange.count;
+            ++filteredParentsCount;
+            KItemRange previousRange = i > 0 ? removedItemRanges.at(i - 1) : KItemRange();
+            // We must check if this caused the range to touch the previous range, if that's the case they shall be merged
+            if (i > 0 && previousRange.index + previousRange.count == itemRange.index) {
+                previousRange.count += itemRange.count;
+                removedItemRanges.replace(i - 1, previousRange);
+                removedItemRanges.removeAt(i);
+            } else {
+                removedItemRanges.replace(i, itemRange);
+                // We must revisit this range in the next iteration since its starting index changed
+                ++i;
+            }
+        }
+    }
+    return filteredParentsCount;
+}
+
 void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
 {
     dispatchPendingItemsToInsert();
@@ -1119,9 +1177,17 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items)
         indexesToRemove = indexesToRemoveWithChildren;
     }
 
-    const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove);
+    KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove);
     removeFilteredChildren(itemRanges);
-    removeItems(itemRanges, DeleteItemData);
+
+    // This call will update itemRanges to include the childless parents that have been filtered.
+    const int filteredParentsCount = filterChildlessParents(itemRanges);
+
+    // If any childless parents were filtered, then itemRanges got updated and now contains items that were really deleted
+    // mixed with expanded folders that are just being filtered out.
+    // If that's the case, we pass 'DeleteItemDataIfUnfiltered' as a hint
+    // so removeItems() will check m_filteredItems to differentiate which is which.
+    removeItems(itemRanges, filteredParentsCount > 0 ? DeleteItemDataIfUnfiltered : DeleteItemData);
 
     Q_EMIT fileItemsChanged(dirsChanged);
 }
@@ -1149,6 +1215,7 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
     QList<ItemData*> newVisibleItems;
 
     QListIterator<QPair<KFileItem, KFileItem> > it(items);
+
     while (it.hasNext()) {
         const QPair<KFileItem, KFileItem>& itemPair = it.next();
         const KFileItem& oldItem = itemPair.first;
@@ -1172,8 +1239,14 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
             }
 
             m_items.remove(oldItem.url());
-            if (newItemMatchesFilter) {
-                m_items.insert(newItem.url(), indexForItem);
+            // We must maintain m_items consistent with m_itemData for now, this very loop is using it.
+            // We leave it to be cleared by removeItems() later, when m_itemData actually gets updated.
+            m_items.insert(newItem.url(), indexForItem);
+            if (newItemMatchesFilter
+                || (itemData->values.value("isExpanded").toBool()
+                    && (indexForItem + 1 < m_itemData.count() && m_itemData.at(indexForItem + 1)->parent == itemData))) {
+                // We are lenient with expanded folders that originally had visible children.
+                // If they become childless now they will be caught by filterChildlessParents()
                 changedFiles.append(newItem);
                 indexes.append(indexForItem);
             } else {
@@ -1184,12 +1257,23 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
             // Check if 'oldItem' is one of the filtered items.
             QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(oldItem);
             if (it != m_filteredItems.end()) {
-                ItemData* itemData = it.value();
+                ItemData *const itemData = it.value();
                 itemData->item = newItem;
 
                 // The data stored in 'values' might have changed. Therefore, we clear
                 // 'values' and re-populate it the next time it is requested via data(int).
+                // Before clearing, we must remember if it was expanded and the expanded parents count,
+                // otherwise these states would be lost. The data() method will deal with this special case.
+                const bool isExpanded = itemData->values.value("isExpanded").toBool();
+                bool hasExpandedParentsCount = false;
+                const int expandedParentsCount = itemData->values.value("expandedParentsCount").toInt(&hasExpandedParentsCount);
                 itemData->values.clear();
+                if (isExpanded) {
+                    itemData->values.insert("isExpanded", true);
+                    if (hasExpandedParentsCount) {
+                        itemData->values.insert("expandedParentsCount", expandedParentsCount);
+                    }
+                }
 
                 m_filteredItems.erase(it);
                 if (newItemMatchesFilter) {
@@ -1201,21 +1285,66 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >&
         }
     }
 
-    // Hide items, previously visible that should get hidden
-    const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes);
+    std::sort(newFilteredIndexes.begin(), newFilteredIndexes.end());
+
+    // We must keep track of parents of new visible items since they must be shown no matter what
+    // They will be considered "immune" to filterChildlessParents()
+    QSet<ItemData *> parentsToEnsureVisible;
+
+    for (ItemData *item : newVisibleItems) {
+        for (ItemData *parent = item->parent; parent && !parentsToEnsureVisible.contains(parent); parent = parent->parent) {
+            parentsToEnsureVisible.insert(parent);
+        }
+    }
+    for (ItemData *parent : parentsToEnsureVisible) {
+        // We make sure they are all unfiltered.
+        if (m_filteredItems.remove(parent->item)) {
+            // If it is being unfiltered now, we mark it to be inserted by appending it to newVisibleItems
+            newVisibleItems.append(parent);
+            // It could be in newFilteredIndexes, we must remove it if it's there:
+            const int parentIndex = index(parent->item);
+            if (parentIndex >= 0) {
+                QVector<int>::iterator it = std::lower_bound(newFilteredIndexes.begin(), newFilteredIndexes.end(), parentIndex);
+                if (it != newFilteredIndexes.end() && *it == parentIndex) {
+                    newFilteredIndexes.erase(it);
+                }
+            }
+        }
+    }
+
+    KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes);
+
+    // This call will update itemRanges to include the childless parents that have been filtered.
+    filterChildlessParents(removedRanges, parentsToEnsureVisible);
+
     removeItems(removedRanges, KeepItemData);
 
     // Show previously hidden items that should get visible
     insertItems(newVisibleItems);
 
+    // Final step: we will emit 'itemsChanged' and 'fileItemsChanged' signals and trigger the asynchronous re-sorting logic.
+
     // If the changed items have been created recently, they might not be in m_items yet.
     // In that case, the list 'indexes' might be empty.
     if (indexes.isEmpty()) {
         return;
     }
 
+    if (newVisibleItems.count() > 0 || removedRanges.count() > 0) {
+        // The original indexes have changed and are now worthless since items were removed and/or inserted.
+        indexes.clear();
+        // m_items is not yet rebuilt at this point, so we use our own means to resolve the new indexes.
+        const QSet<const KFileItem> changedFilesSet(changedFiles.cbegin(), changedFiles.cend());
+        for (int i = 0; i < m_itemData.count(); i++) {
+            if (changedFilesSet.contains(m_itemData.at(i)->item)) {
+                indexes.append(i);
+            }
+        }
+    } else {
+        std::sort(indexes.begin(), indexes.end());
+    }
+
     // Extract the item-ranges out of the changed indexes
-    std::sort(indexes.begin(), indexes.end());
     const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes);
     emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles);
 
@@ -1380,7 +1509,7 @@ void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBe
         removedItemsCount += range.count;
 
         for (int index = range.index; index < range.index + range.count; ++index) {
-            if (behavior == DeleteItemData) {
+            if (behavior == DeleteItemData || (behavior == DeleteItemDataIfUnfiltered && !m_filteredItems.contains(m_itemData.at(index)->item))) {
                 delete m_itemData.at(index);
             }
 
@@ -1424,8 +1553,9 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl&
         determineMimeTypes(items, 200);
     }
 
+    // We search for the parent in m_itemData and then in m_filteredItems if necessary
     const int parentIndex = index(parentUrl);
-    ItemData* parentItem = parentIndex < 0 ? nullptr : m_itemData.at(parentIndex);
+    ItemData *parentItem = parentIndex < 0 ? m_filteredItems.value(KFileItem(parentUrl), nullptr) : m_itemData.at(parentIndex);
 
     QList<ItemData*> itemDataList;
     itemDataList.reserve(items.count());
@@ -1959,6 +2089,19 @@ int KFileItemModel::sortRoleCompare(const ItemData* a, const ItemData* b, const
         break;
     }
 
+   case DimensionsRole: {
+        const QByteArray role = roleForType(m_sortRole);
+        const QSize dimensionsA = a->values.value(role).toSize();
+        const QSize dimensionsB = b->values.value(role).toSize();
+
+        if (dimensionsA.width() == dimensionsB.width()) {
+            result = dimensionsA.height() - dimensionsB.height();
+        } else {
+            result = dimensionsA.width() - dimensionsB.width();
+        }
+        break;
+    }
+
     default: {
         const QByteArray role = roleForType(m_sortRole);
         const QString roleValueA = a->values.value(role).toString();
@@ -2451,40 +2594,41 @@ void KFileItemModel::emitSortProgress(int resolvedCount)
 const KFileItemModel::RoleInfoMap* KFileItemModel::rolesInfoMap(int& count)
 {
     static const RoleInfoMap rolesInfoMap[] = {
-    //  |         role           |        roleType        |                role translation                     |            group translation               | requires Baloo | requires indexer
-        { nullptr,               NoRole,                  nullptr, nullptr,                                     nullptr, nullptr,                            false,           false },
-        { "text",                NameRole,                I18NC_NOOP("@label", "Name"),                 nullptr, nullptr,                            false,           false },
-        { "size",                SizeRole,                I18NC_NOOP("@label", "Size"),                 nullptr, nullptr,                            false,           false },
-        { "modificationtime",    ModificationTimeRole,    I18NC_NOOP("@label", "Modified"),             nullptr, nullptr,                            false,           false },
-        { "creationtime",        CreationTimeRole,        I18NC_NOOP("@label", "Created"),              nullptr, nullptr,                            false,           false },
-        { "accesstime",          AccessTimeRole,          I18NC_NOOP("@label", "Accessed"),             nullptr, nullptr,                            false,           false },
-        { "type",                TypeRole,                I18NC_NOOP("@label", "Type"),                 nullptr, nullptr,                            false,           false },
-        { "rating",              RatingRole,              I18NC_NOOP("@label", "Rating"),               nullptr, nullptr,                            true,            false },
-        { "tags",                TagsRole,                I18NC_NOOP("@label", "Tags"),                 nullptr, nullptr,                            true,            false },
-        { "comment",             CommentRole,             I18NC_NOOP("@label", "Comment"),              nullptr, nullptr,                            true,            false },
-        { "title",               TitleRole,               I18NC_NOOP("@label", "Title"),                I18NC_NOOP("@label", "Document"),    true,            true  },
-        { "wordCount",           WordCountRole,           I18NC_NOOP("@label", "Word Count"),           I18NC_NOOP("@label", "Document"),    true,            true  },
-        { "lineCount",           LineCountRole,           I18NC_NOOP("@label", "Line Count"),           I18NC_NOOP("@label", "Document"),    true,            true  },
-        { "imageDateTime",       ImageDateTimeRole,       I18NC_NOOP("@label", "Date Photographed"),    I18NC_NOOP("@label", "Image"),       true,            true  },
-        { "width",               WidthRole,               I18NC_NOOP("@label", "Width"),                I18NC_NOOP("@label", "Image"),       true,            true  },
-        { "height",              HeightRole,              I18NC_NOOP("@label", "Height"),               I18NC_NOOP("@label", "Image"),       true,            true  },
-        { "orientation",         OrientationRole,         I18NC_NOOP("@label", "Orientation"),          I18NC_NOOP("@label", "Image"),       true,            true  },
-        { "artist",              ArtistRole,              I18NC_NOOP("@label", "Artist"),               I18NC_NOOP("@label", "Audio"),       true,            true  },
-        { "genre",               GenreRole,               I18NC_NOOP("@label", "Genre"),                I18NC_NOOP("@label", "Audio"),       true,            true  },
-        { "album",               AlbumRole,               I18NC_NOOP("@label", "Album"),                I18NC_NOOP("@label", "Audio"),       true,            true  },
-        { "duration",            DurationRole,            I18NC_NOOP("@label", "Duration"),             I18NC_NOOP("@label", "Audio"),       true,            true  },
-        { "bitrate",             BitrateRole,             I18NC_NOOP("@label", "Bitrate"),              I18NC_NOOP("@label", "Audio"),       true,            true  },
-        { "track",               TrackRole,               I18NC_NOOP("@label", "Track"),                I18NC_NOOP("@label", "Audio"),       true,            true  },
-        { "releaseYear",         ReleaseYearRole,         I18NC_NOOP("@label", "Release Year"),         I18NC_NOOP("@label", "Audio"),       true,            true  },
-        { "aspectRatio",         AspectRatioRole,         I18NC_NOOP("@label", "Aspect Ratio"),         I18NC_NOOP("@label", "Video"),       true,            true  },
-        { "frameRate",           FrameRateRole,           I18NC_NOOP("@label", "Frame Rate"),           I18NC_NOOP("@label", "Video"),       true,            true  },
-        { "path",                PathRole,                I18NC_NOOP("@label", "Path"),                 I18NC_NOOP("@label", "Other"),       false,           false },
-        { "deletiontime",        DeletionTimeRole,        I18NC_NOOP("@label", "Deletion Time"),        I18NC_NOOP("@label", "Other"),       false,           false },
-        { "destination",         DestinationRole,         I18NC_NOOP("@label", "Link Destination"),     I18NC_NOOP("@label", "Other"),       false,           false },
-        { "originUrl",           OriginUrlRole,           I18NC_NOOP("@label", "Downloaded From"),      I18NC_NOOP("@label", "Other"),       true,            false },
-        { "permissions",         PermissionsRole,         I18NC_NOOP("@label", "Permissions"),          I18NC_NOOP("@label", "Other"),       false,           false },
-        { "owner",               OwnerRole,               I18NC_NOOP("@label", "Owner"),                I18NC_NOOP("@label", "Other"),       false,           false },
-        { "group",               GroupRole,               I18NC_NOOP("@label", "User Group"),           I18NC_NOOP("@label", "Other"),       false,           false },
+    //  |         role           |        roleType        |                role translation         |         group translation                     | requires Baloo | requires indexer
+        { nullptr,               NoRole,                  KLazyLocalizedString(),                    KLazyLocalizedString(),                            false,           false },
+        { "text",                NameRole,                kli18nc("@label", "Name"),                 KLazyLocalizedString(),                            false,           false },
+        { "size",                SizeRole,                kli18nc("@label", "Size"),                 KLazyLocalizedString(),                            false,           false },
+        { "modificationtime",    ModificationTimeRole,    kli18nc("@label", "Modified"),             KLazyLocalizedString(),                            false,           false },
+        { "creationtime",        CreationTimeRole,        kli18nc("@label", "Created"),              KLazyLocalizedString(),                            false,           false },
+        { "accesstime",          AccessTimeRole,          kli18nc("@label", "Accessed"),             KLazyLocalizedString(),                            false,           false },
+        { "type",                TypeRole,                kli18nc("@label", "Type"),                 KLazyLocalizedString(),                            false,           false },
+        { "rating",              RatingRole,              kli18nc("@label", "Rating"),               KLazyLocalizedString(),                            true,            false },
+        { "tags",                TagsRole,                kli18nc("@label", "Tags"),                 KLazyLocalizedString(),                            true,            false },
+        { "comment",             CommentRole,             kli18nc("@label", "Comment"),              KLazyLocalizedString(),                            true,            false },
+        { "title",               TitleRole,               kli18nc("@label", "Title"),                kli18nc("@label", "Document"),                     true,            true  },
+        { "wordCount",           WordCountRole,           kli18nc("@label", "Word Count"),           kli18nc("@label", "Document"),                     true,            true  },
+        { "lineCount",           LineCountRole,           kli18nc("@label", "Line Count"),           kli18nc("@label", "Document"),                     true,            true  },
+        { "imageDateTime",       ImageDateTimeRole,       kli18nc("@label", "Date Photographed"),    kli18nc("@label", "Image"),                        true,            true  },
+        { "dimensions",          DimensionsRole,          kli18nc("@label width x height", "Dimensions"), kli18nc("@label", "Image"),                   true,            true  },
+        { "width",               WidthRole,               kli18nc("@label", "Width"),                kli18nc("@label", "Image"),                        true,            true  },
+        { "height",              HeightRole,              kli18nc("@label", "Height"),               kli18nc("@label", "Image"),                        true,            true  },
+        { "orientation",         OrientationRole,         kli18nc("@label", "Orientation"),          kli18nc("@label", "Image"),                        true,            true  },
+        { "artist",              ArtistRole,              kli18nc("@label", "Artist"),               kli18nc("@label", "Audio"),                        true,            true  },
+        { "genre",               GenreRole,               kli18nc("@label", "Genre"),                kli18nc("@label", "Audio"),                        true,            true  },
+        { "album",               AlbumRole,               kli18nc("@label", "Album"),                kli18nc("@label", "Audio"),                        true,            true  },
+        { "duration",            DurationRole,            kli18nc("@label", "Duration"),             kli18nc("@label", "Audio"),                        true,            true  },
+        { "bitrate",             BitrateRole,             kli18nc("@label", "Bitrate"),              kli18nc("@label", "Audio"),                        true,            true  },
+        { "track",               TrackRole,               kli18nc("@label", "Track"),                kli18nc("@label", "Audio"),                        true,            true  },
+        { "releaseYear",         ReleaseYearRole,         kli18nc("@label", "Release Year"),         kli18nc("@label", "Audio"),                        true,            true  },
+        { "aspectRatio",         AspectRatioRole,         kli18nc("@label", "Aspect Ratio"),         kli18nc("@label", "Video"),                        true,            true  },
+        { "frameRate",           FrameRateRole,           kli18nc("@label", "Frame Rate"),           kli18nc("@label", "Video"),                        true,            true  },
+        { "path",                PathRole,                kli18nc("@label", "Path"),                 kli18nc("@label", "Other"),                        false,           false },
+        { "deletiontime",        DeletionTimeRole,        kli18nc("@label", "Deletion Time"),        kli18nc("@label", "Other"),                        false,           false },
+        { "destination",         DestinationRole,         kli18nc("@label", "Link Destination"),     kli18nc("@label", "Other"),                        false,           false },
+        { "originUrl",           OriginUrlRole,           kli18nc("@label", "Downloaded From"),      kli18nc("@label", "Other"),                        true,            false },
+        { "permissions",         PermissionsRole,         kli18nc("@label", "Permissions"),          kli18nc("@label", "Other"),                        false,           false },
+        { "owner",               OwnerRole,               kli18nc("@label", "Owner"),                kli18nc("@label", "Other"),                        false,           false },
+        { "group",               GroupRole,               kli18nc("@label", "User Group"),           kli18nc("@label", "Other"),                        false,           false },
     };
 
     count = sizeof(rolesInfoMap) / sizeof(RoleInfoMap);
index 161f6a0e25e3c0d6ae8b7ad794262ffe2f45252d..cc39a0084d21246f3aff0d8dd143c463424b74b4 100644 (file)
@@ -12,6 +12,7 @@
 #include "kitemviews/private/kfileitemmodelfilter.h"
 
 #include <KFileItem>
+#include <KLazyLocalizedString>
 
 #include <QCollator>
 #include <QHash>
@@ -291,7 +292,7 @@ private:
         NoRole, NameRole, SizeRole, ModificationTimeRole, CreationTimeRole, AccessTimeRole, PermissionsRole, OwnerRole,
         GroupRole, TypeRole, DestinationRole, PathRole, DeletionTimeRole,
         // User visible roles available with Baloo:
-        CommentRole, TagsRole, RatingRole, WidthRole, HeightRole, ImageDateTimeRole, OrientationRole,
+        CommentRole, TagsRole, RatingRole, DimensionsRole, WidthRole, HeightRole, ImageDateTimeRole, OrientationRole,
         WordCountRole, TitleRole, LineCountRole, ArtistRole, GenreRole, AlbumRole, DurationRole, TrackRole, ReleaseYearRole,
         BitrateRole, OriginUrlRole, AspectRatioRole, FrameRateRole,
         // Non-visible roles:
@@ -309,7 +310,8 @@ private:
 
     enum RemoveItemsBehavior {
         KeepItemData,
-        DeleteItemData
+        DeleteItemData,
+        DeleteItemDataIfUnfiltered
     };
 
     void insertItems(QList<ItemData*>& items);
@@ -439,10 +441,8 @@ private:
     {
         const char* const role;
         const RoleType roleType;
-        const char* const roleTranslationContext;
-        const char* const roleTranslation;
-        const char* const groupTranslationContext;
-        const char* const groupTranslation;
+        const KLazyLocalizedString roleTranslation;
+        const KLazyLocalizedString groupTranslation;
         const bool requiresBaloo;
         const bool requiresIndexer;
     };
@@ -469,6 +469,15 @@ private:
      */
     bool isConsistent() const;
 
+    /**
+     * Filters out the expanded folders that don't pass the filter themselves and don't have any filter-passing children.
+     * Will update the removedItemRanges arguments to include the parents that have been filtered.
+     * @returns the number of parents that have been filtered.
+     * @param removedItemRanges The ranges of items being deleted/filtered, will get updated
+     * @param parentsToEnsureVisible Parents that must be visible no matter what due to being ancestors of newly visible items
+     */
+    int filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible = QSet<ItemData *>());
+
 private:
     KDirLister *m_dirLister = nullptr;
 
index 978f5df6e0b35abee929ad86be36019557e44100..e13b3dedfd2f75a4cc1993b2fc7944c1d60cd134 100644 (file)
@@ -18,7 +18,7 @@
 #include <KIconLoader>
 #include <KJobWidgets>
 #include <KOverlayIconPlugin>
-#include <KPluginLoader>
+#include <KPluginMetaData>
 #include <KSharedConfig>
 
 #ifdef HAVE_BALOO
@@ -30,6 +30,7 @@
 #include <QApplication>
 #include <QIcon>
 #include <QPainter>
+#include <QPluginLoader>
 #include <QElapsedTimer>
 #include <QTimer>
 
@@ -120,15 +121,16 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO
     connect(m_directoryContentsCounter, &KDirectoryContentsCounter::result,
             this,                       &KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived);
 
-    const auto plugins = KPluginLoader::instantiatePlugins(QStringLiteral("kf5/overlayicon"), nullptr, qApp);
-    for (QObject *it : plugins) {
-        auto plugin = qobject_cast<KOverlayIconPlugin*>(it);
+    const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("kf5/overlayicon"));
+    for (const KPluginMetaData &data : plugins) {
+        auto instance = QPluginLoader(data.fileName()).instance();
+        auto plugin = qobject_cast<KOverlayIconPlugin *>(instance);
         if (plugin) {
             m_overlayIconsPlugin.append(plugin);
             connect(plugin, &KOverlayIconPlugin::overlaysChanged, this, &KFileItemModelRolesUpdater::slotOverlaysChanged);
         } else {
             // not our/valid plugin, so delete the created object
-            it->deleteLater();
+            delete instance;
         }
     }
 }
@@ -1024,7 +1026,7 @@ void KFileItemModelRolesUpdater::startPreviewJob()
 
     KIO::PreviewJob* job = new KIO::PreviewJob(itemSubSet, cacheSize, &m_enabledPlugins);
 
-    job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile() && m_localFileSizePreviewLimit <= 0);
+    job->setIgnoreMaximumSize(itemSubSet.first().isLocalFile() && !itemSubSet.first().isSlow() && m_localFileSizePreviewLimit <= 0);
     if (job->uiDelegate()) {
         KJobWidgets::setWindow(job, qApp->activeWindow());
     }
@@ -1133,7 +1135,7 @@ void KFileItemModelRolesUpdater::loadNextHoverSequencePreview()
     KIO::PreviewJob* job = new KIO::PreviewJob({m_hoverSequenceItem}, cacheSize, &m_enabledPlugins);
 
     job->setSequenceIndex(loadSeqIdx);
-    job->setIgnoreMaximumSize(m_hoverSequenceItem.isLocalFile() && m_localFileSizePreviewLimit <= 0);
+    job->setIgnoreMaximumSize(m_hoverSequenceItem.isLocalFile() && !m_hoverSequenceItem.isSlow() && m_localFileSizePreviewLimit <= 0);
     if (job->uiDelegate()) {
         KJobWidgets::setWindow(job, qApp->activeWindow());
     }
@@ -1405,10 +1407,19 @@ QList<int> KFileItemModelRolesUpdater::indexesToResolve() const
                                (2 * m_maximumVisibleItems)));
 
     // Add visible items.
+    // Resolve files first, their previews are quicker.
+    QList<int> visibleDirs;
     for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) {
-        result.append(i);
+        const KFileItem item = m_model->fileItem(i);
+        if (item.isDir()) {
+            visibleDirs.append(i);
+        } else {
+            result.append(i);
+        }
     }
 
+    result.append(visibleDirs);
+
     // We need a reasonable upper limit for number of items to resolve after
     // and before the visible range. m_maximumVisibleItems can be quite large
     // when using Compact View.
index 8687872ee81b6f75d22fd6577e8bfc8f8c7f3587..80c28f25ca1d98647111da6ba8064390739670f5 100644 (file)
@@ -46,7 +46,7 @@ KItemListController::KItemListController(KItemModelBase* model, KItemListView* v
     m_view(nullptr),
     m_selectionManager(new KItemListSelectionManager(this)),
     m_keyboardManager(new KItemListKeyboardSearchManager(this)),
-    m_pressedIndex(-1),
+    m_pressedIndex(std::nullopt),
     m_pressedMousePos(),
     m_autoActivationTimer(nullptr),
     m_swipeGesture(Qt::CustomGesture),
@@ -574,16 +574,16 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const
         return false;
     }
 
-    if (m_pressedIndex >= 0) {
+    if (m_pressedIndex.has_value() && !m_view->rubberBand()->isActive()) {
         // Check whether a dragging should be started
         if (event->buttons() & Qt::LeftButton) {
             const QPointF pos = transform.map(event->pos());
             if ((pos - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
-                if (!m_selectionManager->isSelected(m_pressedIndex)) {
+                if (!m_selectionManager->isSelected(m_pressedIndex.value())) {
                     // Always assure that the dragged item gets selected. Usually this is already
                     // done on the mouse-press event, but when using the selection-toggle on a
                     // selected item the dragged item is not selected yet.
-                    m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+                    m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
                 } else {
                     // A selected item has been clicked to drag all selected items
                     // -> the selection should not be cleared when the mouse button is released.
@@ -599,12 +599,12 @@ bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const
             QPointF endPos = transform.map(event->pos());
 
             // Update the current item.
-            const int newCurrent = m_view->itemAt(endPos);
-            if (newCurrent >= 0) {
+            const std::optional<int> newCurrent = m_view->itemAt(endPos);
+            if (newCurrent.has_value()) {
                 // It's expected that the new current index is also the new anchor (bug 163451).
                 m_selectionManager->endAnchoredSelection();
-                m_selectionManager->setCurrentItem(newCurrent);
-                m_selectionManager->beginAnchoredSelection(newCurrent);
+                m_selectionManager->setCurrentItem(newCurrent.value());
+                m_selectionManager->beginAnchoredSelection(newCurrent.value());
             }
 
             if (m_view->scrollOrientation() == Qt::Vertical) {
@@ -642,7 +642,7 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, con
         return false;
     }
 
-    Q_EMIT mouseButtonReleased(m_pressedIndex, event->buttons());
+    Q_EMIT mouseButtonReleased(m_pressedIndex.value_or(-1), event->buttons());
 
     return onRelease(transform.map(event->pos()), event->modifiers(), event->button(), false);
 }
@@ -650,21 +650,21 @@ bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, con
 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
 {
     const QPointF pos = transform.map(event->pos());
-    const int index = m_view->itemAt(pos);
+    const std::optional<int> index = m_view->itemAt(pos);
 
     // Expand item if desired - See Bug 295573
     if (m_mouseDoubleClickAction != ActivateItemOnly) {
-        if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
-            const bool expanded = m_model->isExpanded(index);
-            m_model->setExpanded(index, !expanded);
+        if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index.value_or(-1))) {
+            const bool expanded = m_model->isExpanded(index.value());
+            m_model->setExpanded(index.value(), !expanded);
         }
     }
 
     if (event->button() & Qt::RightButton) {
         m_selectionManager->clearSelection();
-        if (index >= 0) {
-            m_selectionManager->setSelected(index);
-            Q_EMIT itemContextMenuRequested(index, event->screenPos());
+        if (index.has_value()) {
+            m_selectionManager->setSelected(index.value());
+            Q_EMIT itemContextMenuRequested(index.value(), event->screenPos());
         } else {
             const QRectF headerBounds = m_view->headerBoundaries();
             if (headerBounds.contains(event->pos())) {
@@ -678,9 +678,9 @@ bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event,
 
     bool emitItemActivated = !(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) &&
                              (event->button() & Qt::LeftButton) &&
-                             index >= 0 && index < m_model->count();
+                             index.has_value() && index.value() < m_model->count();
     if (emitItemActivated) {
-        Q_EMIT itemActivated(index);
+        Q_EMIT itemActivated(index.value());
     }
     return false;
 }
@@ -805,7 +805,7 @@ bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QT
         Q_EMIT aboveItemDropEvent(dropAboveIndex, event);
     } else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) {
         // Something has been dropped on an item or on an empty part of the view.
-        Q_EMIT itemDropEvent(m_view->itemAt(pos), event);
+        Q_EMIT itemDropEvent(m_view->itemAt(pos).value_or(-1), event);
     }
 
     QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd);
@@ -828,27 +828,76 @@ bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const
         return false;
     }
 
-    KItemListWidget* oldHoveredWidget = hoveredWidget();
-    const QPointF pos = transform.map(event->pos());
-    KItemListWidget* newHoveredWidget = widgetForPos(pos);
+    // We identify the widget whose expansionArea had been hovered before this hoverMoveEvent() triggered.
+    // we can't use hoveredWidget() here (it handles the icon+text rect, not the expansion rect)
+    // like hoveredWidget(), we find the hovered widget for the expansion rect
+    const auto visibleItemListWidgets = m_view->visibleItemListWidgets();
+    const auto oldHoveredExpansionWidgetIterator = std::find_if(visibleItemListWidgets.begin(), visibleItemListWidgets.end(), [](auto &widget) {
+        return widget->expansionAreaHovered();
+    });
+    const auto oldHoveredExpansionWidget = oldHoveredExpansionWidgetIterator == visibleItemListWidgets.end() ?
+                                           std::nullopt : std::make_optional(*oldHoveredExpansionWidgetIterator);
 
-    if (oldHoveredWidget != newHoveredWidget) {
-        if (oldHoveredWidget) {
+    const auto unhoverOldHoveredWidget = [&]() {
+        if (auto oldHoveredWidget = hoveredWidget(); oldHoveredWidget) {
+            // handle the text+icon one
             oldHoveredWidget->setHovered(false);
             Q_EMIT itemUnhovered(oldHoveredWidget->index());
         }
+    };
 
-        if (newHoveredWidget) {
-            newHoveredWidget->setHovered(true);
-            const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
-            newHoveredWidget->setHoverPosition(mappedPos);
-            Q_EMIT itemHovered(newHoveredWidget->index());
+    const auto unhoverOldExpansionWidget = [&]() {
+        if (oldHoveredExpansionWidget) {
+            // then the expansion toggle
+            (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
         }
-    } else if (oldHoveredWidget) {
-        const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos);
-        oldHoveredWidget->setHoverPosition(mappedPos);
-    }
+    };
+
+    const QPointF pos = transform.map(event->pos());
+    if (KItemListWidget *newHoveredWidget = widgetForPos(pos); newHoveredWidget) {
+        // something got hovered, work out which part and set hover for the appropriate widget
+        const auto mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
+        const bool isOnExpansionToggle = newHoveredWidget->expansionToggleRect().contains(mappedPos);
+
+        if (isOnExpansionToggle) {
+            // make sure we unhover the old one first if old!=new
+            if (oldHoveredExpansionWidget && *oldHoveredExpansionWidget != newHoveredWidget) {
+                (*oldHoveredExpansionWidget)->setExpansionAreaHovered(false);
+            }
+            // we also unhover any old icon+text hovers, in case the mouse movement from icon+text to expansion toggle is too fast (i.e. newHoveredWidget is never null between the transition)
+            unhoverOldHoveredWidget();
+
 
+            newHoveredWidget->setExpansionAreaHovered(true);
+        } else {
+            // make sure we unhover the old one first if old!=new
+            if (auto oldHoveredWidget = hoveredWidget(); oldHoveredWidget && oldHoveredWidget != newHoveredWidget) {
+                oldHoveredWidget->setHovered(false);
+                Q_EMIT itemUnhovered(oldHoveredWidget->index());
+            }
+            // we also unhover any old expansion toggle hovers, in case the mouse movement from expansion toggle to icon+text is too fast (i.e. newHoveredWidget is never null between the transition)
+            unhoverOldExpansionWidget();
+
+            const bool isOverIconAndText = newHoveredWidget->iconRect().contains(mappedPos) || newHoveredWidget->textRect().contains(mappedPos);
+            const bool hasMultipleSelection = m_selectionManager->selectedItems().count() > 1;
+
+            if (hasMultipleSelection && !isOverIconAndText) {
+                // In case we have multiple selections, clicking on any row will deselect the selection.
+                // So, as a visual cue for signalling that clicking anywhere won't select, but clear current highlights,
+                // we disable hover of the *row*(i.e. blank space to the right of the icon+text)
+
+                // (no-op in this branch for masked hover)
+            } else {
+                newHoveredWidget->setHovered(true);
+                newHoveredWidget->setHoverPosition(mappedPos);
+                Q_EMIT itemHovered(newHoveredWidget->index());
+            }
+        }
+    } else {
+        // unhover any currently hovered expansion and text+icon widgets
+        unhoverOldHoveredWidget();
+        unhoverOldExpansionWidget();
+    }
     return false;
 }
 
@@ -998,10 +1047,10 @@ void KItemListController::tapAndHoldTriggered(QGestureEvent* event, const QTrans
         m_pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
         m_pressedIndex = m_view->itemAt(m_pressedMousePos);
 
-        if (m_pressedIndex >= 0 && !m_selectionManager->isSelected(m_pressedIndex)) {
+        if (m_pressedIndex.has_value() && !m_selectionManager->isSelected(m_pressedIndex.value())) {
             m_selectionManager->clearSelection();
-            m_selectionManager->setSelected(m_pressedIndex);
-        } else if (m_pressedIndex == -1) {
+            m_selectionManager->setSelected(m_pressedIndex.value());
+        } else if (!m_pressedIndex.has_value()) {
             m_selectionManager->clearSelection();
             startRubberBand();
         }
@@ -1064,9 +1113,9 @@ void KItemListController::swipeTriggered(QGestureEvent* event, const QTransform&
         Q_EMIT scrollerStop();
 
         if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
-            Q_EMIT mouseButtonPressed(m_pressedIndex, Qt::BackButton);
+            Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::BackButton);
         } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
-            Q_EMIT mouseButtonPressed(m_pressedIndex, Qt::ForwardButton);
+            Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), Qt::ForwardButton);
         } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
             Q_EMIT swipeUp();
         }
@@ -1085,7 +1134,7 @@ void KItemListController::twoFingerTapTriggered(QGestureEvent* event, const QTra
     if (twoTap->state() == Qt::GestureStarted) {
         m_pressedMousePos = transform.map(twoTap->pos());
         m_pressedIndex = m_view->itemAt(m_pressedMousePos);
-        if (m_pressedIndex >= 0) {
+        if (m_pressedIndex.has_value()) {
             onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
             onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
         }
@@ -1303,10 +1352,7 @@ KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const
     const auto widgets = m_view->visibleItemListWidgets();
     for (KItemListWidget* widget : widgets) {
         const QPointF mappedPos = widget->mapFromItem(m_view, pos);
-
-        const bool hovered = widget->contains(mappedPos) &&
-                             !widget->expansionToggleRect().contains(mappedPos);
-        if (hovered) {
+        if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
             return widget;
         }
     }
@@ -1419,7 +1465,7 @@ void KItemListController::updateExtendedSelectionRegion()
 
 bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
 {
-    Q_EMIT mouseButtonPressed(m_pressedIndex, buttons);
+    Q_EMIT mouseButtonPressed(m_pressedIndex.value_or(-1), buttons);
 
     if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
         // Do not select items when clicking the back/forward buttons, see
@@ -1427,26 +1473,27 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
         return true;
     }
 
-    if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
+    if (m_view->isAboveExpansionToggle(m_pressedIndex.value_or(-1), m_pressedMousePos)) {
         m_selectionManager->endAnchoredSelection();
-        m_selectionManager->setCurrentItem(m_pressedIndex);
-        m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+        m_selectionManager->setCurrentItem(m_pressedIndex.value());
+        m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
         return true;
     }
 
-    m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
+    m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), m_pressedMousePos);
     if (m_selectionTogglePressed) {
-        m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+        m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
         // The previous anchored selection has been finished already in
         // KItemListSelectionManager::setSelected(). We can safely change
         // the current item and start a new anchored selection now.
-        m_selectionManager->setCurrentItem(m_pressedIndex);
-        m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+        m_selectionManager->setCurrentItem(m_pressedIndex.value());
+        m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
         return true;
     }
 
     const bool shiftPressed = modifiers & Qt::ShiftModifier;
     const bool controlPressed = modifiers & Qt::ControlModifier;
+    const bool rightClick = buttons & Qt::RightButton;
 
     // The previous selection is cleared if either
     // 1. The selection mode is SingleSelection, or
@@ -1456,11 +1503,35 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
     //       - start dragging multiple items, or
     //       - open the context menu and perform an action for all selected items.
     const bool shiftOrControlPressed = shiftPressed || controlPressed;
-    const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex);
+    const bool pressedItemAlreadySelected = m_pressedIndex.has_value() && m_selectionManager->isSelected(m_pressedIndex.value());
     const bool clearSelection = m_selectionBehavior == SingleSelection ||
                                 (!shiftOrControlPressed && !pressedItemAlreadySelected);
+
+
+    // When this method returns false, a rubberBand selection is created using KItemListController::startRubberBand via the caller.
     if (clearSelection) {
+        const int selectedItemsCount = m_selectionManager->selectedItems().count();
         m_selectionManager->clearSelection();
+        // clear and bail when we got an existing multi-selection
+        if (selectedItemsCount  > 1 && m_pressedIndex.has_value()) {
+            const auto row = m_view->m_visibleItems.value(m_pressedIndex.value());
+            const auto mappedPos = row->mapFromItem(m_view, pos);
+            if (pressedItemAlreadySelected || row->iconRect().contains(mappedPos) || row->textRect().contains(mappedPos)) {
+                // we are indeed inside the text/icon rect, keep m_pressedIndex what it is
+                // and short-circuit for single-click activation (it will then propagate to onRelease and activate the item)
+                // or we just keep going for double-click activation
+                if (m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) {
+                    return true; // event handled, don't create rubber band
+                }
+            } else {
+                // we're not inside the text/icon rect, as we've already cleared the selection
+                // we can just stop here and make sure handlers down the line (i.e. onRelease) don't activate
+                m_pressedIndex.reset();
+                // we don't stop event propagation and proceed to create a rubber band and let onRelease
+                // decide (based on m_pressedIndex) whether we're in a drag (drag => new rubber band, click => don't select the item)
+                return false;
+            }
+        }
     } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (buttons & Qt::LeftButton)) {
         // The user might want to start dragging multiple items, but if he clicks the item
         // in order to trigger it instead, the other selected items must be deselected.
@@ -1469,8 +1540,8 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
         //    clear the selection in mouseReleaseEvent(), unless the items are dragged.
         m_clearSelectionIfItemsAreNotDragged = true;
 
-        if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex, m_pressedMousePos)) {
-            Q_EMIT selectedItemTextPressed(m_pressedIndex);
+        if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex.value_or(-1), m_pressedMousePos)) {
+            Q_EMIT selectedItemTextPressed(m_pressedIndex.value_or(-1));
         }
     }
 
@@ -1479,7 +1550,14 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
         m_selectionManager->endAnchoredSelection();
     }
 
-    if (buttons & Qt::RightButton) {
+    if (rightClick) {
+
+        // Do header hit check and short circuit before commencing any state changing effects
+        if (m_view->headerBoundaries().contains(pos)) {
+            Q_EMIT headerContextMenuRequested(screenPos);
+            return true;
+        }
+
         // Stop rubber band from persisting after right-clicks
         KItemListRubberBand* rubberBand = m_view->rubberBand();
         if (rubberBand->isActive()) {
@@ -1489,25 +1567,37 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
         }
     }
 
-    if (m_pressedIndex >= 0) {
-        m_selectionManager->setCurrentItem(m_pressedIndex);
+    if (m_pressedIndex.has_value()) {
+        m_selectionManager->setCurrentItem(m_pressedIndex.value());
+        const auto row = m_view->m_visibleItems.value(m_pressedIndex.value()); // anything outside of row.contains() will be the empty region of the row rect
+        const bool hitTargetIsRowEmptyRegion = !row->contains(row->mapFromItem(m_view, pos));
+        // again, when this method returns false, a rubberBand selection is created as the event is not consumed;
+        // createRubberBand here tells us whether to return true or false.
+        bool createRubberBand = (hitTargetIsRowEmptyRegion && m_selectionManager->selectedItems().isEmpty());
+
+        if (rightClick && hitTargetIsRowEmptyRegion) {
+            // we got a right click outside the text rect, default to action on the current url and not the pressed item
+            Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos);
+            return true;
+        }
 
         switch (m_selectionBehavior) {
         case NoSelection:
             break;
 
         case SingleSelection:
-            m_selectionManager->setSelected(m_pressedIndex);
+            m_selectionManager->setSelected(m_pressedIndex.value());
             break;
 
         case MultiSelection:
             if (controlPressed && !shiftPressed) {
-                m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
-                m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+                m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
+                m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
+                createRubberBand = false; // multi selection, don't propagate any further
             } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
                 // Select the pressed item and start a new anchored selection
-                m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
-                m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+                m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
+                m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
             }
             break;
 
@@ -1516,20 +1606,16 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
             break;
         }
 
-        if (buttons & Qt::RightButton) {
-            Q_EMIT itemContextMenuRequested(m_pressedIndex, screenPos);
+        if (rightClick) {
+            Q_EMIT itemContextMenuRequested(m_pressedIndex.value(), screenPos);
         }
-
-        return true;
+        return !createRubberBand;
     }
 
-    if (buttons & Qt::RightButton) {
-        const QRectF headerBounds = m_view->headerBoundaries();
-        if (headerBounds.contains(pos)) {
-            Q_EMIT headerContextMenuRequested(screenPos);
-        } else {
-            Q_EMIT viewContextMenuRequested(screenPos);
-        }
+    if (rightClick) {
+        // header right click handling would have been done before this so just normal context
+        // menu here is fine
+        Q_EMIT viewContextMenuRequested(screenPos);
         return true;
     }
 
@@ -1538,14 +1624,14 @@ bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, c
 
 bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
 {
-    const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
+    const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex.value_or(-1), m_pressedMousePos);
     if (isAboveSelectionToggle) {
         m_selectionTogglePressed = false;
         return true;
     }
 
     if (!isAboveSelectionToggle && m_selectionTogglePressed) {
-        m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
+        m_selectionManager->setSelected(m_pressedIndex.value_or(-1), 1, KItemListSelectionManager::Toggle);
         m_selectionTogglePressed = false;
         return true;
     }
@@ -1554,60 +1640,77 @@ bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifi
     const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier ||
                                        controlPressed;
 
+    const std::optional<int> index = m_view->itemAt(pos);
+
     KItemListRubberBand* rubberBand = m_view->rubberBand();
+    bool rubberBandRelease = false;
     if (rubberBand->isActive()) {
         disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
         rubberBand->setActive(false);
         m_oldSelection.clear();
         m_view->setAutoScroll(false);
+        rubberBandRelease = true;
+        // We check for actual rubber band drag here: if delta between start and end is less than drag threshold,
+        // then we have a single click on one of the rows
+        if ((rubberBand->endPosition() - rubberBand->startPosition()).manhattanLength() < QApplication::startDragDistance()) {
+            rubberBandRelease = false; // since we're only selecting, unmark rubber band release flag
+            // m_pressedIndex will have no value if we came from a multi-selection clearing onPress
+            // in that case, we don't select anything
+            if (index.has_value() && m_pressedIndex.has_value()) {
+                if (controlPressed && m_selectionBehavior == MultiSelection) {
+                    m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Toggle);
+                } else {
+                    m_selectionManager->setSelected(index.value());
+                }
+                if (!m_selectionManager->isAnchoredSelectionActive()) {
+                    m_selectionManager->beginAnchoredSelection(index.value());
+                }
+            }
+        }
     }
 
-    const int index = m_view->itemAt(pos);
-
-    if (index >= 0 && index == m_pressedIndex) {
+    if (index.has_value() && index == m_pressedIndex) {
         // The release event is done above the same item as the press event
 
         if (m_clearSelectionIfItemsAreNotDragged) {
             // A selected item has been clicked, but no drag operation has been started
             // -> clear the rest of the selection.
             m_selectionManager->clearSelection();
-            m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
-            m_selectionManager->beginAnchoredSelection(m_pressedIndex);
+            m_selectionManager->setSelected(m_pressedIndex.value(), 1, KItemListSelectionManager::Select);
+            m_selectionManager->beginAnchoredSelection(m_pressedIndex.value());
         }
 
         if (buttons & Qt::LeftButton) {
             bool emitItemActivated = true;
-            if (m_view->isAboveExpansionToggle(index, pos)) {
-                const bool expanded = m_model->isExpanded(index);
-                m_model->setExpanded(index, !expanded);
+            if (m_view->isAboveExpansionToggle(index.value(), pos)) {
+                const bool expanded = m_model->isExpanded(index.value());
+                m_model->setExpanded(index.value(), !expanded);
 
-                Q_EMIT itemExpansionToggleClicked(index);
+                Q_EMIT itemExpansionToggleClicked(index.value());
                 emitItemActivated = false;
-            } else if (shiftOrControlPressed) {
-                // The mouse click should only update the selection, not trigger the item
+            } else if (shiftOrControlPressed && m_selectionBehavior != SingleSelection) {
+                // The mouse click should only update the selection, not trigger the item, except when
+                // we are in single selection mode
                 emitItemActivated = false;
-                // When Ctrl-clicking an item when in single selection mode
-                // i.e. where Ctrl won't change the selection, pretend it was middle clicked
-                if (controlPressed && m_selectionBehavior == SingleSelection) {
-                    Q_EMIT itemMiddleClicked(index);
-                }
-            } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) {
-                if (touch) {
-                emitItemActivated = true;
+            } else {
+                const bool singleClickActivation = m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced;
+                if (!singleClickActivation) {
+                    emitItemActivated = touch;
                 } else {
-                emitItemActivated = false;
+                    // activate on single click only if we didn't come from a rubber band release
+                    emitItemActivated = !rubberBandRelease;
                 }
             }
             if (emitItemActivated) {
-                Q_EMIT itemActivated(index);
+                Q_EMIT itemActivated(index.value());
             }
         } else if (buttons & Qt::MiddleButton) {
-            Q_EMIT itemMiddleClicked(index);
+            Q_EMIT itemMiddleClicked(index.value());
         }
     }
 
     m_pressedMousePos = QPointF();
-    m_pressedIndex = -1;
+    m_pressedIndex = std::nullopt;
     m_clearSelectionIfItemsAreNotDragged = false;
     return false;
 }
index 24339134e717ebf696ca6d4ffd23639b0a6015fc..5fe195e9f490b7411b592f2cebd01f9c79332c9c 100644 (file)
@@ -9,6 +9,8 @@
 #ifndef KITEMLISTCONTROLLER_H
 #define KITEMLISTCONTROLLER_H
 
+#include <optional>
+
 #include "dolphin_export.h"
 #include "kitemset.h"
 
@@ -137,7 +139,7 @@ Q_SIGNALS:
      * Is emitted if more than one item has been activated by pressing Return/Enter
      * when having a selection.
      */
-    void itemsActivated(const KItemSetindexes);
+    void itemsActivated(const KItemSet &indexes);
 
     void itemMiddleClicked(int index);
 
@@ -329,7 +331,7 @@ private:
     KItemListView* m_view;
     KItemListSelectionManager* m_selectionManager;
     KItemListKeyboardSearchManager* m_keyboardManager;
-    int m_pressedIndex;
+    std::optional<int> m_pressedIndex;
     QPointF m_pressedMousePos;
 
     QTimer* m_autoActivationTimer;
index 80dd94149b6bea2b62cc15ce8f11e314ee0806f6..f0ff52503de4313ef8b372cc346fbd9b3c109733 100644 (file)
@@ -161,6 +161,7 @@ void KItemListGroupHeader::resizeEvent(QGraphicsSceneResizeEvent* event)
     if (event->oldSize().height() != event->newSize().height()) {
         m_dirtyCache = true;
     }
+    updateSize();
 }
 
 void KItemListGroupHeader::updateCache()
@@ -174,6 +175,13 @@ void KItemListGroupHeader::updateCache()
     m_separatorColor = mixedColor(c1, c2, 10);
     m_roleColor = mixedColor(c1, c2, 60);
 
+    updateSize();
+
+    m_dirtyCache = false;
+}
+
+void KItemListGroupHeader::updateSize()
+{
     const int padding = qMax(1, m_styleOption.padding);
     const int horizontalMargin = qMax(2, m_styleOption.horizontalMargin);
 
@@ -187,7 +195,7 @@ void KItemListGroupHeader::updateCache()
                           size().width() - 2 * padding - horizontalMargin,
                           roleHeight);
 
-    m_dirtyCache = false;
+    update();
 }
 
 QColor KItemListGroupHeader::mixedColor(const QColor& c1, const QColor& c2, int c1Percent)
index 4ba4690561196bd1043afb37eddee02fea785983..48af1e9e0d12267c4806833fd34c52f6e0bd9cc9 100644 (file)
@@ -94,6 +94,7 @@ protected:
 
 private:
     void updateCache();
+    void updateSize();
 
     static QColor mixedColor(const QColor& c1, const QColor& c2, int c1Percent = 50);
 
index dedeb57e3a9f2d2c2eb6fa36e3e0680f6a6604a5..22e70603b0516ebeced4dbdc7cda40105a108308 100644 (file)
@@ -61,6 +61,20 @@ qreal KItemListHeader::preferredColumnWidth(const QByteArray& role) const
     return m_headerWidget->preferredColumnWidth(role);
 }
 
+void KItemListHeader::setLeadingPadding(qreal width){
+    if (m_headerWidget->leadingPadding() != width) {
+        m_headerWidget->setLeadingPadding(width);
+        if (m_headerWidget->automaticColumnResizing()) {
+            m_view->applyAutomaticColumnWidths();
+        }
+        m_view->doLayout(KItemListView::NoAnimation);
+    }
+}
+
+qreal KItemListHeader::leadingPadding() const{
+    return m_headerWidget->leadingPadding();
+}
+
 KItemListHeader::KItemListHeader(KItemListView* listView) :
     QObject(listView),
     m_view(listView)
@@ -72,5 +86,7 @@ KItemListHeader::KItemListHeader(KItemListView* listView) :
             this, &KItemListHeader::columnWidthChanged);
     connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChangeFinished,
             this, &KItemListHeader::columnWidthChangeFinished);
+    connect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+            this, &KItemListHeader::leadingPaddingChanged);
 }
 
index 6875a0f5e75cd804a51a72c2d27bf9b8a35f696c..e2653e41eab68d041a2e616eb3b3bf41f5a336b0 100644 (file)
@@ -58,7 +58,16 @@ public:
      */
     qreal preferredColumnWidth(const QByteArray& role) const;
 
+    /**
+     * Sets the width of the column *before* the first column.
+     * This is intended to facilitate an empty region for deselection in the main viewport.
+     */
+    void setLeadingPadding(qreal width);
+    qreal leadingPadding() const;
+
 Q_SIGNALS:
+    void leadingPaddingChanged(qreal width);
+
     /**
      * Is emitted if the width of a column has been adjusted by the user with the mouse
      * (no signal is emitted if KItemListHeader::setColumnWidth() is invoked).
index 5c8c712e8ba11d6a8aea62e07be41f730202a1a0..86583db1ec958a25ed5b3fa7e1ea3febec725f76 100644 (file)
@@ -389,7 +389,7 @@ qreal KItemListView::verticalPageStep() const
     return size().height() - headerHeight;
 }
 
-int KItemListView::itemAt(const QPointF& pos) const
+std::optional<int> KItemListView::itemAt(const QPointF& pos) const
 {
     QHashIterator<int, KItemListWidget*> it(m_visibleItems);
     while (it.hasNext()) {
@@ -397,12 +397,12 @@ int KItemListView::itemAt(const QPointF& pos) const
 
         const KItemListWidget* widget = it.value();
         const QPointF mappedPos = widget->mapFromItem(this, pos);
-        if (widget->contains(mappedPos)) {
+        if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) {
             return it.key();
         }
     }
 
-    return -1;
+    return std::nullopt;
 }
 
 bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const
@@ -458,7 +458,7 @@ int KItemListView::lastVisibleIndex() const
     return m_layouter->lastVisibleIndex();
 }
 
-void KItemListView::calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint) const
+void KItemListView::calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint) const
 {
     widgetCreator()->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, this);
 }
@@ -477,6 +477,32 @@ bool KItemListView::supportsItemExpanding() const
     return m_supportsItemExpanding;
 }
 
+void KItemListView::setHighlightEntireRow(bool highlightEntireRow)
+{
+    if (m_highlightEntireRow != highlightEntireRow) {
+        m_highlightEntireRow = highlightEntireRow;
+        onHighlightEntireRowChanged(highlightEntireRow);
+    }
+}
+
+bool KItemListView::highlightEntireRow() const
+{
+    return m_highlightEntireRow;
+}
+
+void KItemListView::setAlternateBackgrounds(bool alternate)
+{
+    if (m_alternateBackgrounds != alternate) {
+        m_alternateBackgrounds = alternate;
+        updateAlternateBackgrounds();
+    }
+}
+
+bool KItemListView::alternateBackgrounds() const
+{
+    return m_alternateBackgrounds;
+}
+
 QRectF KItemListView::itemRect(int index) const
 {
     return m_layouter->itemRect(index);
@@ -495,6 +521,11 @@ QRectF KItemListView::itemContextRect(int index) const
     return contextRect;
 }
 
+bool KItemListView::isElided(int index) const
+{
+    return m_sizeHintResolver->isElided(index);
+}
+
 void KItemListView::scrollToItem(int index)
 {
     QRectF viewGeometry = geometry();
@@ -576,6 +607,8 @@ void KItemListView::setHeaderVisible(bool visible)
 
         connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged,
                 this, &KItemListView::slotHeaderColumnWidthChanged);
+        connect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+                this, &KItemListView::slotLeadingPaddingChanged);
         connect(m_headerWidget, &KItemListHeaderWidget::columnMoved,
                 this, &KItemListView::slotHeaderColumnMoved);
         connect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged,
@@ -588,6 +621,8 @@ void KItemListView::setHeaderVisible(bool visible)
     } else if (!visible && m_headerWidget->isVisible()) {
         disconnect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged,
                    this, &KItemListView::slotHeaderColumnWidthChanged);
+        disconnect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+                this, &KItemListView::slotLeadingPaddingChanged);
         disconnect(m_headerWidget, &KItemListHeaderWidget::columnMoved,
                    this, &KItemListView::slotHeaderColumnMoved);
         disconnect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged,
@@ -748,7 +783,7 @@ void KItemListView::setItemSize(const QSizeF& size)
                                                 size,
                                                 m_layouter->itemMargin());
 
-    const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) &&
+    const bool alternateBackgroundsChanged = m_alternateBackgrounds &&
                                              (( m_itemSize.isEmpty() && !size.isEmpty()) ||
                                               (!m_itemSize.isEmpty() && size.isEmpty()));
 
@@ -924,6 +959,11 @@ void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, co
     Q_UNUSED(previous)
 }
 
+void KItemListView::onHighlightEntireRowChanged(bool highlightEntireRow)
+{
+    Q_UNUSED(highlightEntireRow)
+}
+
 void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
 {
     Q_UNUSED(supportsExpanding)
@@ -1516,6 +1556,16 @@ void KItemListView::slotHeaderColumnWidthChanged(const QByteArray& role,
     doLayout(NoAnimation);
 }
 
+void KItemListView::slotLeadingPaddingChanged(qreal width)
+{
+    Q_UNUSED(width)
+    if (m_headerWidget->automaticColumnResizing()) {
+        applyAutomaticColumnWidths();
+    }
+    applyColumnWidthsFromHeader();
+    doLayout(NoAnimation);
+}
+
 void KItemListView::slotHeaderColumnMoved(const QByteArray& role,
                                           int currentIndex,
                                           int previousIndex)
@@ -2220,7 +2270,7 @@ void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget)
 
 bool KItemListView::useAlternateBackgrounds() const
 {
-    return m_itemSize.isEmpty() && m_visibleRoles.count() > 1;
+    return m_alternateBackgrounds && m_itemSize.isEmpty();
 }
 
 QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const
@@ -2237,11 +2287,11 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi
     const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin);
     for (const QByteArray& visibleRole : qAsConst(m_visibleRoles)) {
         const QString headerText = m_model->roleDescription(visibleRole);
-        const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2;
+        const qreal headerWidth = fontMetrics.horizontalAdvance(headerText) + gripMargin + headerMargin * 2;
         widths.insert(visibleRole, headerWidth);
     }
 
-    // Calculate the preferred column withs for each item and ignore values
+    // Calculate the preferred column widths for each item and ignore values
     // smaller than the width for showing the headline unclipped.
     const KItemListWidgetCreatorBase* creator = widgetCreator();
     int calculatedItemCount = 0;
@@ -2278,7 +2328,7 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi
 void KItemListView::applyColumnWidthsFromHeader()
 {
     // Apply the new size to the layouter
-    const qreal requiredWidth = columnWidthsSum();
+    const qreal requiredWidth = columnWidthsSum() + m_headerWidget->leadingPadding();
     const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth),
                                  m_itemSize.height());
     m_layouter->setItemSize(dynamicItemSize);
@@ -2296,6 +2346,7 @@ void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget)
     for (const QByteArray& role : qAsConst(m_visibleRoles)) {
         widget->setColumnWidth(role, m_headerWidget->columnWidth(role));
     }
+    widget->setLeadingPadding(m_headerWidget->leadingPadding());
 }
 
 void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges)
@@ -2373,7 +2424,7 @@ void KItemListView::applyAutomaticColumnWidths()
     qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole);
     QSizeF dynamicItemSize = m_itemSize;
 
-    qreal requiredWidth = columnWidthsSum();
+    qreal requiredWidth = columnWidthsSum() + m_headerWidget->leadingPadding();
     const qreal availableWidth = size().width();
     if (requiredWidth < availableWidth) {
         // Stretch the first column to use the whole remaining width
index 760e0a4157e115bb08dd090e4fd45b5a85af8444..228989cc40b63621d86efa719de3c21fdb81c6bc 100644 (file)
@@ -9,6 +9,8 @@
 #ifndef KITEMLISTVIEW_H
 #define KITEMLISTVIEW_H
 
+#include <optional>
+
 #include "dolphin_export.h"
 #include "kitemviews/kitemliststyleoption.h"
 #include "kitemviews/kitemlistwidget.h"
@@ -160,10 +162,10 @@ public:
      * @return Index of the item that is below the point \a pos.
      *         The position is relative to the upper right of
      *         the visible area. Only (at least partly) visible
-     *         items are considered. -1 is returned if no item is
-     *         below the position.
+     *         items are considered. std::nullopt is returned if
+     *         no item is below the position.
      */
-    int itemAt(const QPointF& pos) const;
+    std::optional<int> itemAt(const QPointF& pos) const;
     bool isAboveSelectionToggle(int index, const QPointF& pos) const;
     bool isAboveExpansionToggle(int index, const QPointF& pos) const;
     bool isAboveText(int index, const QPointF& pos) const;
@@ -189,7 +191,7 @@ public:
      * @note the logical height (width) is actually the
      * width (height) if the scroll orientation is Qt::Vertical!
      */
-    void calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint) const;
+    void calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint) const;
 
     /**
      * If set to true, items having child-items can be expanded to show the child-items as
@@ -203,6 +205,12 @@ public:
     void setSupportsItemExpanding(bool supportsExpanding);
     bool supportsItemExpanding() const;
 
+    void setHighlightEntireRow(bool highlightEntireRow);
+    bool highlightEntireRow() const;
+
+    void setAlternateBackgrounds(bool alternate);
+    bool alternateBackgrounds() const;
+
     /**
      * @return The rectangle of the item relative to the top/left of
      *         the currently visible area (see KItemListView::offset()).
@@ -221,6 +229,12 @@ public:
      */
     QRectF itemContextRect(int index) const;
 
+    /**
+     * @return Whether or not the name of the file has been elided. At present this will
+     *         only ever be true when in icons view.
+     */
+    bool isElided(int index) const;
+
     /**
      * Scrolls to the item with the index \a index so that the item
      * will be fully visible.
@@ -366,6 +380,7 @@ protected:
     virtual void onScrollOffsetChanged(qreal current, qreal previous);
     virtual void onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous);
     virtual void onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous);
+    virtual void onHighlightEntireRowChanged(bool highlightEntireRow);
     virtual void onSupportsItemExpandingChanged(bool supportsExpanding);
 
     virtual void onTransactionBegin();
@@ -384,6 +399,8 @@ protected:
     virtual void updateFont();
     virtual void updatePalette();
 
+    KItemListSizeHintResolver* m_sizeHintResolver;
+
 protected Q_SLOTS:
     virtual void slotItemsInserted(const KItemRangeList& itemRanges);
     virtual void slotItemsRemoved(const KItemRangeList& itemRanges);
@@ -416,6 +433,8 @@ private Q_SLOTS:
                                       qreal currentWidth,
                                       qreal previousWidth);
 
+    void slotLeadingPaddingChanged(qreal width);
+
     /**
      * Is invoked if a column has been moved by the user. Applies
      * the moved role to the view.
@@ -597,7 +616,7 @@ private:
 
     /**
      * Resizes the column-widths of m_headerWidget based on the preferred widths
-     * and the vailable view-size.
+     * and the available view-size.
      */
     void applyAutomaticColumnWidths();
 
@@ -697,6 +716,8 @@ private:
 private:
     bool m_enabledSelectionToggles;
     bool m_grouped;
+    bool m_highlightEntireRow;
+    bool m_alternateBackgrounds;
     bool m_supportsItemExpanding;
     bool m_editingRole;
     int m_activeTransactions; // Counter for beginTransaction()/endTransaction()
@@ -723,7 +744,6 @@ private:
     QHash<int, Cell> m_visibleCells;
 
     int m_scrollBarExtent;
-    KItemListSizeHintResolver* m_sizeHintResolver;
     KItemListViewLayouter* m_layouter;
     KItemListViewAnimation* m_animation;
 
@@ -803,7 +823,7 @@ public:
 
     virtual void recycle(KItemListWidget* widget);
 
-    virtual void calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const = 0;
+    virtual void calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const = 0;
 
     virtual qreal preferredRoleColumnWidth(const QByteArray& role,
                                            int index,
@@ -822,7 +842,7 @@ public:
 
     KItemListWidget* create(KItemListView* view) override;
 
-    void calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const override;
+    void calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const override;
 
     qreal preferredRoleColumnWidth(const QByteArray& role,
                                            int index,
@@ -856,7 +876,7 @@ KItemListWidget* KItemListWidgetCreator<T>::create(KItemListView* view)
 }
 
 template<class T>
-void KItemListWidgetCreator<T>::calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KItemListWidgetCreator<T>::calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
 {
     return m_informant->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, view);
 }
@@ -873,7 +893,7 @@ qreal KItemListWidgetCreator<T>::preferredRoleColumnWidth(const QByteArray& role
  * @brief Base class for creating KItemListGroupHeaders.
  *
  * It is recommended that applications simply use the KItemListGroupHeaderCreator-template class.
- * For a custom implementation the methods create() and recyle() must be reimplemented.
+ * For a custom implementation the methods create() and recycle() must be reimplemented.
  * The intention of the group-header creator is to prevent repetitive and expensive instantiations and
  * deletions of KItemListGroupHeaders by recycling existing header instances.
  */
index ffc088bdbf9348eed96b6a1d97aaed6d4af1895e..a1afadff4bd93fc234628e8a1fea1c97f84c800a 100644 (file)
@@ -5,7 +5,6 @@
  */
 
 #ifndef QT_NO_ACCESSIBILITY
-
 #include "kitemlistviewaccessible.h"
 
 #include "kitemlistcontainer.h"
@@ -202,8 +201,8 @@ QAccessible::State KItemListViewAccessible::state() const
 QAccessibleInterface* KItemListViewAccessible::childAt(int x, int y) const
 {
     const QPointF point = QPointF(x, y);
-    int itemIndex = view()->itemAt(view()->mapFromScene(point));
-    return child(itemIndex);
+    const std::optional<int> itemIndex = view()->itemAt(view()->mapFromScene(point));
+    return child(itemIndex.value_or(-1));
 }
 
 QAccessibleInterface* KItemListViewAccessible::parent() const
index 69a38432a4e21fe360b2610858c67ce21cd9f71b..79ffee2102c8d89134da1e4fdf89a1cd6022cc77 100644 (file)
@@ -34,6 +34,7 @@ KItemListWidget::KItemListWidget(KItemListWidgetInformant* informant, QGraphicsI
     m_selected(false),
     m_current(false),
     m_hovered(false),
+    m_expansionAreaHovered(false),
     m_alternateBackground(false),
     m_enabledSelectionToggle(false),
     m_data(),
@@ -180,6 +181,18 @@ qreal KItemListWidget::columnWidth(const QByteArray& role) const
     return m_columnWidths.value(role);
 }
 
+qreal KItemListWidget::leadingPadding() const {
+    return m_leadingPadding;
+}
+
+void KItemListWidget::setLeadingPadding(qreal width) {
+    if (m_leadingPadding != width){
+        m_leadingPadding = width;
+        leadingPaddingChanged(width);
+        update();
+    }
+}
+
 void KItemListWidget::setStyleOption(const KItemListStyleOption& option)
 {
     if (m_styleOption == option) {
@@ -280,6 +293,20 @@ bool KItemListWidget::isHovered() const
     return m_hovered;
 }
 
+void KItemListWidget::setExpansionAreaHovered(bool hovered)
+{
+    if (hovered == m_expansionAreaHovered) {
+        return;
+    }
+    m_expansionAreaHovered = hovered;
+    update();
+}
+
+bool KItemListWidget::expansionAreaHovered() const
+{
+    return m_expansionAreaHovered;
+}
+
 void KItemListWidget::setHoverPosition(const QPointF& pos)
 {
     if (m_selectionToggle) {
@@ -416,6 +443,11 @@ void KItemListWidget::columnWidthChanged(const QByteArray& role,
     Q_UNUSED(previous)
 }
 
+void KItemListWidget::leadingPaddingChanged(qreal width)
+{
+    Q_UNUSED(width)
+}
+
 void KItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
                                          const KItemListStyleOption& previous)
 {
index 2de82c6fb4c27c09da3e0af55623b6444eb1e0ad..0b82266c43b87a704dc727d3dc590f2b9af4851a 100644 (file)
@@ -35,7 +35,7 @@ public:
     KItemListWidgetInformant();
     virtual ~KItemListWidgetInformant();
 
-    virtual void calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const = 0;
+    virtual void calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const = 0;
 
     virtual qreal preferredRoleColumnWidth(const QByteArray& role,
                                            int index,
@@ -80,6 +80,9 @@ public:
     void setColumnWidth(const QByteArray& role, qreal width);
     qreal columnWidth(const QByteArray& role) const;
 
+    void setLeadingPadding(qreal width);
+    qreal leadingPadding() const;
+
     void setStyleOption(const KItemListStyleOption& option);
     const KItemListStyleOption& styleOption() const;
 
@@ -94,6 +97,9 @@ public:
     void setHovered(bool hovered);
     bool isHovered() const;
 
+    void setExpansionAreaHovered(bool hover);
+    bool expansionAreaHovered() const;
+
     void setHoverPosition(const QPointF& pos);
 
     void setAlternateBackground(bool enable);
@@ -182,6 +188,7 @@ protected:
     virtual void dataChanged(const QHash<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>());
     virtual void visibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous);
     virtual void columnWidthChanged(const QByteArray& role, qreal current, qreal previous);
+    virtual void leadingPaddingChanged(qreal width);
     virtual void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous);
     virtual void currentChanged(bool current);
     virtual void selectedChanged(bool selected);
@@ -190,6 +197,7 @@ protected:
     virtual void siblingsInformationChanged(const QBitArray& current, const QBitArray& previous);
     virtual void editedRoleChanged(const QByteArray& current, const QByteArray& previous);
     void resizeEvent(QGraphicsSceneResizeEvent* event) override;
+    void clearHoverCache();
 
     /**
      * Called when the user starts hovering this item.
@@ -225,7 +233,6 @@ private Q_SLOTS:
 private:
     void initializeSelectionToggle();
     void setHoverOpacity(qreal opacity);
-    void clearHoverCache();
     void drawItemStyleOption(QPainter* painter, QWidget* widget, QStyle::State styleState);
 
 private:
@@ -236,11 +243,13 @@ private:
     bool m_selected;
     bool m_current;
     bool m_hovered;
+    bool m_expansionAreaHovered;
     bool m_alternateBackground;
     bool m_enabledSelectionToggle;
     QHash<QByteArray, QVariant> m_data;
     QList<QByteArray> m_visibleRoles;
     QHash<QByteArray, qreal> m_columnWidths;
+    qreal m_leadingPadding;
     KItemListStyleOption m_styleOption;
     QBitArray m_siblingsInfo;
 
diff --git a/src/kitemviews/kstandarditem.cpp b/src/kitemviews/kstandarditem.cpp
deleted file mode 100644 (file)
index fcaa50b..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "kstandarditem.h"
-#include "kstandarditemmodel.h"
-
-KStandardItem::KStandardItem(KStandardItem* parent) :
-    QObject(parent),
-    m_model(nullptr),
-    m_data()
-{
-}
-
-KStandardItem::KStandardItem(const QString& text, KStandardItem* parent) :
-    QObject(parent),
-    m_model(nullptr),
-    m_data()
-{
-    setText(text);
-}
-
-KStandardItem::KStandardItem(const QString& icon, const QString& text, KStandardItem* parent) :
-    QObject(parent),
-    m_model(nullptr),
-    m_data()
-{
-    setIcon(icon);
-    setText(text);
-}
-
-KStandardItem::~KStandardItem()
-{
-}
-
-void KStandardItem::setText(const QString& text)
-{
-    setDataValue("text", text);
-}
-
-QString KStandardItem::text() const
-{
-    return m_data["text"].toString();
-}
-
-void KStandardItem::setIcon(const QString& icon)
-{
-    setDataValue("iconName", icon);
-}
-
-QString KStandardItem::icon() const
-{
-    return m_data["iconName"].toString();
-}
-
-void KStandardItem::setIconOverlays(const QStringList& overlays)
-{
-    setDataValue("iconOverlays", overlays);
-}
-
-QStringList KStandardItem::iconOverlays() const
-{
-    return m_data["iconOverlays"].toStringList();
-}
-
-void KStandardItem::setGroup(const QString& group)
-{
-    setDataValue("group", group);
-}
-
-QString KStandardItem::group() const
-{
-    return m_data["group"].toString();
-}
-
-void KStandardItem::setDataValue(const QByteArray& role, const QVariant& value)
-{
-    const QVariant previous = m_data.value(role);
-    if (previous == value) {
-        return;
-    }
-
-    m_data.insert(role, value);
-    onDataValueChanged(role, value, previous);
-
-    if (m_model) {
-        const int index = m_model->index(this);
-        QSet<QByteArray> changedRoles;
-        changedRoles.insert(role);
-        m_model->onItemChanged(index, changedRoles);
-        Q_EMIT m_model->itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
-    }
-}
-
-QVariant KStandardItem::dataValue(const QByteArray& role) const
-{
-    return m_data[role];
-}
-
-void KStandardItem::setData(const QHash<QByteArray, QVariant>& values)
-{
-    const QHash<QByteArray, QVariant> previous = m_data;
-    m_data = values;
-    onDataChanged(values, previous);
-}
-
-QHash<QByteArray, QVariant> KStandardItem::data() const
-{
-    return m_data;
-}
-
-void KStandardItem::onDataValueChanged(const QByteArray& role,
-                                       const QVariant& current,
-                                       const QVariant& previous)
-{
-    Q_UNUSED(role)
-    Q_UNUSED(current)
-    Q_UNUSED(previous)
-}
-
-void KStandardItem::onDataChanged(const QHash<QByteArray, QVariant>& current,
-                                  const QHash<QByteArray, QVariant>& previous)
-{
-    Q_UNUSED(current)
-    Q_UNUSED(previous)
-}
-
diff --git a/src/kitemviews/kstandarditem.h b/src/kitemviews/kstandarditem.h
deleted file mode 100644 (file)
index fb64b33..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef KSTANDARDITEM_H
-#define KSTANDARDITEM_H
-
-#include "dolphin_export.h"
-
-#include <QByteArray>
-#include <QHash>
-#include <QObject>
-#include <QSet>
-#include <QVariant>
-
-class KStandardItemModel;
-
-/**
- * @brief Represents and item of KStandardItemModel.
- *
- * Provides setter- and getter-methods for the most commonly
- * used roles. It is possible to assign values for custom
- * roles by using setDataValue().
- */
-class DOLPHIN_EXPORT KStandardItem : public QObject
-{
-    Q_OBJECT
-public:
-    explicit KStandardItem(KStandardItem* parent = nullptr);
-    explicit KStandardItem(const QString& text, KStandardItem* parent = nullptr);
-    KStandardItem(const QString& icon, const QString& text, KStandardItem* parent = nullptr);
-    ~KStandardItem() override;
-
-    /**
-     * Sets the text for the "text"-role.
-     */
-    void setText(const QString& text);
-    QString text() const;
-
-    /**
-     * Sets the icon for the "iconName"-role.
-     */
-    void setIcon(const QString& icon);
-    QString icon() const;
-
-    void setIconOverlays(const QStringList& overlays);
-    QStringList iconOverlays() const;
-
-    /**
-     * Sets the group for the "group"-role.
-     */
-    void setGroup(const QString& group);
-    QString group() const;
-
-    void setDataValue(const QByteArray& role, const QVariant& value);
-    QVariant dataValue(const QByteArray& role) const;
-
-    void setData(const QHash<QByteArray, QVariant>& values);
-    QHash<QByteArray, QVariant> data() const;
-
-protected:
-    virtual void onDataValueChanged(const QByteArray& role,
-                                    const QVariant& current,
-                                    const QVariant& previous);
-
-    virtual void onDataChanged(const QHash<QByteArray, QVariant>& current,
-                               const QHash<QByteArray, QVariant>& previous);
-
-private:
-    KStandardItemModel* m_model;
-
-    QHash<QByteArray, QVariant> m_data;
-
-    friend class KStandardItemModel;
-};
-
-#endif
-
-
index 22eefe5eac7e2202e0159e8117e885a7b41df30f..28497ddfc2dbff5011097425d5d420ae14e7c622 100644 (file)
@@ -17,8 +17,7 @@ KStandardItemListGroupHeader::KStandardItemListGroupHeader(QGraphicsWidget* pare
     m_text(),
     m_pixmap()
 {
-    m_text.setTextFormat(Qt::PlainText);
-    m_text.setPerformanceHint(QStaticText::AggressiveCaching);
+
 }
 
 KStandardItemListGroupHeader::~KStandardItemListGroupHeader()
@@ -37,7 +36,7 @@ void KStandardItemListGroupHeader::paintRole(QPainter* painter, const QRectF& ro
 {
     if (m_pixmap.isNull()) {
         painter->setPen(color);
-        painter->drawStaticText(roleBounds.topLeft(), m_text);
+        painter->drawText(roleBounds, 0, m_text);
     } else {
         painter->drawPixmap(roleBounds.topLeft(), m_pixmap);
     }
@@ -55,7 +54,11 @@ void KStandardItemListGroupHeader::paintSeparator(QPainter* painter, const QColo
     if (scrollOrientation() == Qt::Horizontal) {
         painter->drawLine(0, 0, 0, size().height() - 1);
     } else {
-        painter->drawLine(0, 0, size().width() - 1, 0);
+        if (layoutDirection() == Qt::LeftToRight) {
+            painter->drawLine(0, 0, size().width() - 1, 0);
+        } else {
+            painter->drawLine(1, 0, size().width(), 0);
+        }
     }
 }
 
@@ -75,7 +78,7 @@ void KStandardItemListGroupHeader::dataChanged(const QVariant& current, const QV
 
 void KStandardItemListGroupHeader::resizeEvent(QGraphicsSceneResizeEvent* event)
 {
-    QGraphicsWidget::resizeEvent(event);
+    KItemListGroupHeader::resizeEvent(event);
     m_dirtyCache = true;
 }
 
@@ -87,7 +90,7 @@ void KStandardItemListGroupHeader::updateCache()
     const qreal maxWidth = size().width() - 4 * styleOption().padding;
 
     if (role() == "rating") {
-        m_text.setText(QString());
+        m_text = QString();
 
         const qreal height = styleOption().fontMetrics.ascent();
         const QSizeF pixmapSize(qMin(height * 5, maxWidth), height);
@@ -104,7 +107,7 @@ void KStandardItemListGroupHeader::updateCache()
 
         QFontMetricsF fontMetrics(font());
         const QString text = fontMetrics.elidedText(data().toString(), Qt::ElideRight, maxWidth);
-        m_text.setText(text);
+        m_text = text;
     }
 }
 
index ff428c4eae801a88fbde162e0ae6071f978a7b8a..965bf995cf7bd48b28da2fc968885c69b05a7468 100644 (file)
@@ -35,7 +35,7 @@ private:
 
 private:
     bool m_dirtyCache;
-    QStaticText m_text;
+    QString m_text;
     QPixmap m_pixmap;
 };
 #endif
index 6edbefad80fc2f519f66ac0a8c8027ddd085aed8..4b7c2d9a42ac84224fd23d55e47aa448fa8faae9 100644 (file)
@@ -17,6 +17,7 @@ KStandardItemListView::KStandardItemListView(QGraphicsWidget* parent) :
     setAcceptDrops(true);
     setScrollOrientation(Qt::Vertical);
     setVisibleRoles({"text"});
+    setAlternateBackgrounds(true);
 }
 
 KStandardItemListView::~KStandardItemListView()
@@ -34,6 +35,8 @@ void KStandardItemListView::setItemLayout(ItemLayout layout)
     const ItemLayout previous = m_itemLayout;
     m_itemLayout = layout;
 
+    // keep the leading padding option unchanged here
+    setHighlightEntireRow(layout == DetailsLayout);
     setSupportsItemExpanding(itemLayoutSupportsItemExpanding(layout));
     setScrollOrientation(layout == CompactLayout ? Qt::Horizontal : Qt::Vertical);
 
@@ -69,6 +72,7 @@ void KStandardItemListView::initializeItemListWidget(KItemListWidget* item)
     default:            Q_ASSERT(false); break;
     }
 
+    standardItemListWidget->setHighlightEntireRow(highlightEntireRow());
     standardItemListWidget->setSupportsItemExpanding(supportsItemExpanding());
 }
 
index 9c527fa171deb2e4b74672b9cf6ca40c8e642e67..668382cd519f15a2ba6504ea27d381dcfc049164 100644 (file)
@@ -36,7 +36,7 @@ KStandardItemListWidgetInformant::~KStandardItemListWidgetInformant()
 {
 }
 
-void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
 {
     switch (static_cast<const KStandardItemListView*>(view)->itemLayout()) {
     case KStandardItemListView::IconsLayout:
@@ -121,7 +121,7 @@ QFont KStandardItemListWidgetInformant::customizedFontForLinks(const QFont& base
     return baseFont;
 }
 
-void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
 {
     const KItemListStyleOption& option = view->styleOption();
     const QFont& normalFont = option.font;
@@ -138,7 +138,7 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector
     textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
 
     for (int index = 0; index < logicalHeightHints.count(); ++index) {
-        if (logicalHeightHints.at(index) > 0.0) {
+        if (logicalHeightHints.at(index).first > 0.0) {
             continue;
         }
 
@@ -146,7 +146,7 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector
         const QFont& font = itemIsLink(index, view) ? linkFont : normalFont;
 
         const QString& text = KStringHandler::preProcessWrap(itemText(index, view));
-
+        
         // Calculate the number of lines required for wrapping the name
         qreal textHeight = 0;
         QTextLayout layout(text, font);
@@ -154,6 +154,7 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector
         layout.beginLayout();
         QTextLine line;
         int lineCount = 0;
+        bool isElided = false;
         while ((line = layout.createLine()).isValid()) {
             line.setLineWidth(maxWidth);
             line.naturalTextWidth();
@@ -161,6 +162,7 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector
 
             ++lineCount;
             if (lineCount == option.maxTextLines) {
+                isElided = layout.createLine().isValid();
                 break;
             }
         }
@@ -169,13 +171,14 @@ void KStandardItemListWidgetInformant::calculateIconsLayoutItemSizeHints(QVector
         // Add one line for each additional information
         textHeight += additionalRolesSpacing;
 
-        logicalHeightHints[index] = textHeight + spacingAndIconHeight;
+        logicalHeightHints[index].first = textHeight + spacingAndIconHeight;
+        logicalHeightHints[index].second = isElided;
     }
 
     logicalWidthHint = itemWidth;
 }
 
-void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
 {
     const KItemListStyleOption& option = view->styleOption();
     const QFontMetrics& normalFontMetrics = option.fontMetrics;
@@ -190,7 +193,7 @@ void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVect
     const QFontMetrics linkFontMetrics(customizedFontForLinks(option.font));
 
     for (int index = 0; index < logicalHeightHints.count(); ++index) {
-        if (logicalHeightHints.at(index) > 0.0) {
+        if (logicalHeightHints.at(index).first > 0.0) {
             continue;
         }
 
@@ -217,17 +220,17 @@ void KStandardItemListWidgetInformant::calculateCompactLayoutItemSizeHints(QVect
             width = maxWidth;
         }
 
-        logicalHeightHints[index] = width;
+        logicalHeightHints[index].first = width;
     }
 
     logicalWidthHint = height;
 }
 
-void KStandardItemListWidgetInformant::calculateDetailsLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
+void KStandardItemListWidgetInformant::calculateDetailsLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const
 {
     const KItemListStyleOption& option = view->styleOption();
     const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height());
-    logicalHeightHints.fill(height);
+    logicalHeightHints.fill(std::make_pair(height, false));
     logicalWidthHint = -1.0;
 }
 
@@ -247,6 +250,7 @@ KStandardItemListWidget::KStandardItemListWidget(KItemListWidgetInformant* infor
     m_pixmapPos(),
     m_pixmap(),
     m_scaledPixmapSize(),
+    m_columnWidthSum(),
     m_iconRect(),
     m_hoverPixmap(),
     m_textRect(),
@@ -290,6 +294,18 @@ KStandardItemListWidget::Layout KStandardItemListWidget::layout() const
     return m_layout;
 }
 
+void KStandardItemListWidget::setHighlightEntireRow(bool highlightEntireRow) {
+    if (m_highlightEntireRow != highlightEntireRow) {
+        m_highlightEntireRow = highlightEntireRow;
+        m_dirtyLayout = true;
+        update();
+    }
+}
+
+bool KStandardItemListWidget::highlightEntireRow() const {
+    return m_highlightEntireRow;
+}
+
 void KStandardItemListWidget::setSupportsItemExpanding(bool supportsItemExpanding)
 {
     if (m_supportsItemExpanding != supportsItemExpanding) {
@@ -491,7 +507,11 @@ QRectF KStandardItemListWidget::selectionRect() const
     case DetailsLayout: {
         const int padding = styleOption().padding;
         QRectF adjustedIconRect = iconRect().adjusted(-padding, -padding, padding, padding);
-        return adjustedIconRect | m_textRect;
+        QRectF result = adjustedIconRect | m_textRect;
+        if (m_highlightEntireRow) {
+            result.setRight(m_columnWidthSum + leadingPadding());
+        }
+        return result;
     }
 
     default:
@@ -708,6 +728,11 @@ void KStandardItemListWidget::columnWidthChanged(const QByteArray& role,
     m_dirtyLayout = true;
 }
 
+void KStandardItemListWidget::leadingPaddingChanged(qreal padding) {
+    Q_UNUSED(padding)
+    m_dirtyLayout = true;
+}
+
 void KStandardItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
                                              const KItemListStyleOption& previous)
 {
@@ -900,10 +925,13 @@ void KStandardItemListWidget::triggerCacheRefreshing()
     m_isHidden = isHidden();
     m_customizedFont = customizedFont(styleOption().font);
     m_customizedFontMetrics = QFontMetrics(m_customizedFont);
+    m_columnWidthSum = std::accumulate(m_sortedVisibleRoles.begin(), m_sortedVisibleRoles.end(),
+                                       qreal(), [this](qreal sum, const auto &role){ return sum + columnWidth(role); });
 
     updateExpansionArea();
     updateTextsCache();
     updatePixmapCache();
+    clearHoverCache();
 
     m_dirtyLayout = false;
     m_dirtyContent = false;
@@ -921,7 +949,8 @@ void KStandardItemListWidget::updateExpansionArea()
             const qreal inc = (widgetHeight - option.iconSize) / 2;
             const qreal x = expandedParentsCount * widgetHeight + inc;
             const qreal y = inc;
-            m_expansionArea = QRectF(x, y, option.iconSize, option.iconSize);
+            const qreal xPadding = m_highlightEntireRow ? leadingPadding() : 0;
+            m_expansionArea = QRectF(xPadding + x, y, option.iconSize, option.iconSize);
             return;
         }
     }
@@ -1358,7 +1387,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
     if (m_supportsItemExpanding) {
         firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2;
     } else {
-        firstColumnInc += option.padding;
+        firstColumnInc += option.padding + leadingPadding();
     }
 
     qreal x = firstColumnInc;
@@ -1374,7 +1403,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
 
         const bool isTextRole = (role == "text");
         if (isTextRole) {
-            availableTextWidth -= firstColumnInc;
+            availableTextWidth -= firstColumnInc - leadingPadding();
         }
 
         if (requiredWidth > availableTextWidth) {
@@ -1396,7 +1425,7 @@ void KStandardItemListWidget::updateDetailsLayoutTextCache()
 
             // The column after the name should always be aligned on the same x-position independent
             // from the expansion-level shown in the name column
-            x -= firstColumnInc;
+            x -= firstColumnInc - leadingPadding();
         } else if (isRoleRightAligned(role)) {
             textInfo->pos.rx() += roleWidth - requiredWidth - columnWidthInc;
         }
@@ -1408,8 +1437,11 @@ void KStandardItemListWidget::updateAdditionalInfoTextColor()
     QColor c1;
     if (m_customTextColor.isValid()) {
         c1 = m_customTextColor;
-    } else if (isSelected() && m_layout != DetailsLayout) {
-        c1 = styleOption().palette.highlightedText().color();
+    } else if (isSelected()) {
+        // The detail text colour needs to match the main text (HighlightedText) for the same level
+        // of readability. We short circuit early here to avoid interpolating with another colour.
+        m_additionalInfoTextColor = styleOption().palette.color(QPalette::HighlightedText);
+        return;
     } else {
         c1 = styleOption().palette.text().color();
     }
@@ -1448,15 +1480,15 @@ void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter)
     const int x = (m_expansionArea.left() + m_expansionArea.right() - siblingSize) / 2;
     QRect siblingRect(x, 0, siblingSize, siblingSize);
 
-    QStyleOption option;
-    option.palette.setColor(QPalette::Text, option.palette.color(normalTextColorRole()));
     bool isItemSibling = true;
 
     const QBitArray siblings = siblingsInformation();
+    QStyleOption option;
+    const auto normalColor = option.palette.color(normalTextColorRole());
+    const auto highlightColor = option.palette.color(expansionAreaHovered() ? QPalette::Highlight : normalTextColorRole());
     for (int i = siblings.count() - 1; i >= 0; --i) {
         option.rect = siblingRect;
         option.state = siblings.at(i) ? QStyle::State_Sibling : QStyle::State_None;
-
         if (isItemSibling) {
             option.state |= QStyle::State_Item;
             if (m_isExpandable) {
@@ -1465,7 +1497,10 @@ void KStandardItemListWidget::drawSiblingsInformation(QPainter* painter)
             if (data().value("isExpanded").toBool()) {
                 option.state |= QStyle::State_Open;
             }
+            option.palette.setColor(QPalette::Text, highlightColor);
             isItemSibling = false;
+        } else {
+            option.palette.setColor(QPalette::Text, normalColor);
         }
 
         style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter);
index 7d33419bcbc15f92b91a877705eece66d359abab..3edec0db55c8e75d4ddf6f0cd9004fbc7787a6ff 100644 (file)
@@ -23,12 +23,13 @@ class DOLPHIN_EXPORT KStandardItemListWidgetInformant : public KItemListWidgetIn
 public:
     KStandardItemListWidgetInformant();
     ~KStandardItemListWidgetInformant() override;
-
-    void calculateItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const override;
+    
+    void calculateItemSizeHints(QVector<std::pair<qreal /* height */, bool /* isElided */>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const override;
 
     qreal preferredRoleColumnWidth(const QByteArray& role,
                                            int index,
                                            const KItemListView* view) const override;
+
 protected:
     /**
      * @return The value of the "text" role. The default implementation returns
@@ -59,9 +60,9 @@ protected:
     */
     virtual QFont customizedFontForLinks(const QFont& baseFont) const;
 
-    void calculateIconsLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
-    void calculateCompactLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
-    void calculateDetailsLayoutItemSizeHints(QVector<qreal>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
+    void calculateIconsLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
+    void calculateCompactLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
+    void calculateDetailsLayoutItemSizeHints(QVector<std::pair<qreal, bool>>& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const;
 
     friend class KStandardItemListWidget; // Accesses roleText()
 };
@@ -87,6 +88,9 @@ public:
     void setLayout(Layout layout);
     Layout layout() const;
 
+    void setHighlightEntireRow(bool highlightEntireRow);
+    bool highlightEntireRow() const;
+
     void setSupportsItemExpanding(bool supportsItemExpanding);
     bool supportsItemExpanding() const;
 
@@ -167,6 +171,7 @@ protected:
     void dataChanged(const QHash<QByteArray, QVariant>& current, const QSet<QByteArray>& roles = QSet<QByteArray>()) override;
     void visibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous) override;
     void columnWidthChanged(const QByteArray& role, qreal current, qreal previous) override;
+    void leadingPaddingChanged(qreal width) override;
     void styleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) override;
     void hoveredChanged(bool hovered) override;
     void selectedChanged(bool selected) override;
@@ -240,6 +245,7 @@ private:
     QFont m_customizedFont;
     QFontMetrics m_customizedFontMetrics;
     bool m_isExpandable;
+    bool m_highlightEntireRow;
     bool m_supportsItemExpanding;
 
     bool m_dirtyLayout;
@@ -251,6 +257,7 @@ private:
     QPixmap m_pixmap;
     QSize m_scaledPixmapSize; //Size of the pixmap in device independent pixels
 
+    qreal m_columnWidthSum;
     QRectF m_iconRect;          // Cache for KItemListWidget::iconRect()
     QPixmap m_hoverPixmap;      // Cache for modified m_pixmap when hovering the item
 
diff --git a/src/kitemviews/kstandarditemmodel.cpp b/src/kitemviews/kstandarditemmodel.cpp
deleted file mode 100644 (file)
index 128841c..0000000
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "kstandarditemmodel.h"
-
-#include "kstandarditem.h"
-
-KStandardItemModel::KStandardItemModel(QObject* parent) :
-    KItemModelBase(parent),
-    m_items(),
-    m_indexesForItems()
-{
-}
-
-KStandardItemModel::~KStandardItemModel()
-{
-    qDeleteAll(m_items);
-    m_items.clear();
-    m_indexesForItems.clear();
-}
-
-void KStandardItemModel::insertItem(int index, KStandardItem* item)
-{
-    if (index < 0 || index > count() || !item) {
-        delete item;
-        return;
-    }
-
-    if (!m_indexesForItems.contains(item)) {
-        item->m_model = this;
-        m_items.insert(index, item);
-        m_indexesForItems.insert(item, index);
-
-        // Inserting an item requires to update the indexes
-        // afterwards from m_indexesForItems.
-        for (int i = index + 1; i < m_items.count(); ++i) {
-            m_indexesForItems.insert(m_items[i], i);
-        }
-
-        // TODO: no hierarchical items are handled yet
-
-        onItemInserted(index);
-        Q_EMIT itemsInserted(KItemRangeList() << KItemRange(index, 1));
-    }
-}
-
-void KStandardItemModel::changeItem(int index, KStandardItem* item)
-{
-    if (index < 0 || index >= count() || !item) {
-        delete item;
-        return;
-    }
-
-    item->m_model = this;
-
-    QSet<QByteArray> changedRoles;
-
-    KStandardItem* oldItem = m_items[index];
-    const QHash<QByteArray, QVariant> oldData = oldItem->data();
-    const QHash<QByteArray, QVariant> newData = item->data();
-
-    // Determine which roles have been changed
-    QHashIterator<QByteArray, QVariant> it(oldData);
-    while (it.hasNext()) {
-        it.next();
-        const QByteArray role = it.key();
-        const QVariant oldValue = it.value();
-        if (newData.contains(role) && newData.value(role) != oldValue) {
-            changedRoles.insert(role);
-        }
-    }
-
-    m_indexesForItems.remove(oldItem);
-    delete oldItem;
-    oldItem = nullptr;
-
-    m_items[index] = item;
-    m_indexesForItems.insert(item, index);
-
-    onItemChanged(index, changedRoles);
-    Q_EMIT itemsChanged(KItemRangeList() << KItemRange(index, 1), changedRoles);
-}
-
-void KStandardItemModel::removeItem(int index)
-{
-    if (index >= 0 && index < count()) {
-        KStandardItem* item = m_items[index];
-        m_indexesForItems.remove(item);
-        m_items.removeAt(index);
-
-        // Removing an item requires to update the indexes
-        // afterwards from m_indexesForItems.
-        for (int i = index; i < m_items.count(); ++i) {
-            m_indexesForItems.insert(m_items[i], i);
-        }
-
-        onItemRemoved(index, item);
-
-        item->deleteLater();
-        item = nullptr;
-
-        Q_EMIT itemsRemoved(KItemRangeList() << KItemRange(index, 1));
-
-        // TODO: no hierarchical items are handled yet
-    }
-}
-
-void KStandardItemModel::clear()
-{
-    int size = m_items.size();
-    m_items.clear();
-    m_indexesForItems.clear();
-
-    Q_EMIT itemsRemoved(KItemRangeList() << KItemRange(0, size));
-}
-
-KStandardItem* KStandardItemModel::item(int index) const
-{
-    if (index < 0 || index >= m_items.count()) {
-        return nullptr;
-    }
-    return m_items[index];
-}
-
-int KStandardItemModel::index(const KStandardItem* item) const
-{
-    return m_indexesForItems.value(item, -1);
-}
-
-void KStandardItemModel::appendItem(KStandardItem *item)
-{
-    insertItem(m_items.count(), item);
-}
-
-int KStandardItemModel::count() const
-{
-    return m_items.count();
-}
-
-QHash<QByteArray, QVariant> KStandardItemModel::data(int index) const
-{
-    if (index >= 0 && index < count()) {
-        const KStandardItem* item = m_items[index];
-        if (item) {
-            return item->data();
-        }
-    }
-    return QHash<QByteArray, QVariant>();
-}
-
-bool KStandardItemModel::setData(int index, const QHash<QByteArray, QVariant>& values)
-{
-    Q_UNUSED(values)
-    if (index < 0 || index >= count()) {
-        return false;
-    }
-
-    return true;
-}
-
-QMimeData* KStandardItemModel::createMimeData(const KItemSet& indexes) const
-{
-    Q_UNUSED(indexes)
-    return nullptr;
-}
-
-int KStandardItemModel::indexForKeyboardSearch(const QString& text, int startFromIndex) const
-{
-    Q_UNUSED(text)
-    Q_UNUSED(startFromIndex)
-    return -1;
-}
-
-bool KStandardItemModel::supportsDropping(int index) const
-{
-    Q_UNUSED(index)
-    return false;
-}
-
-QString KStandardItemModel::roleDescription(const QByteArray& role) const
-{
-    Q_UNUSED(role)
-    return QString();
-}
-
-QList<QPair<int, QVariant> > KStandardItemModel::groups() const
-{
-    QList<QPair<int, QVariant> > groups;
-
-    const QByteArray role = sortRole().isEmpty() ? "group" : sortRole();
-    bool isFirstGroupValue = true;
-    QString groupValue;
-    const int maxIndex = count() - 1;
-    for (int i = 0; i <= maxIndex; ++i) {
-        const QString newGroupValue = m_items.at(i)->dataValue(role).toString();
-        if (newGroupValue != groupValue || isFirstGroupValue) {
-            groupValue = newGroupValue;
-            groups.append(QPair<int, QVariant>(i, newGroupValue));
-            isFirstGroupValue = false;
-        }
-    }
-
-    return groups;
-}
-
-void KStandardItemModel::onItemInserted(int index)
-{
-    Q_UNUSED(index)
-}
-
-void KStandardItemModel::onItemChanged(int index, const QSet<QByteArray>& changedRoles)
-{
-    Q_UNUSED(index)
-    Q_UNUSED(changedRoles)
-}
-
-void KStandardItemModel::onItemRemoved(int index, KStandardItem* removedItem)
-{
-    Q_UNUSED(index)
-    Q_UNUSED(removedItem)
-}
-
diff --git a/src/kitemviews/kstandarditemmodel.h b/src/kitemviews/kstandarditemmodel.h
deleted file mode 100644 (file)
index d92ec5d..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef KSTANDARDITEMMODEL_H
-#define KSTANDARDITEMMODEL_H
-
-#include "dolphin_export.h"
-#include "kitemviews/kitemmodelbase.h"
-
-#include <QHash>
-#include <QList>
-
-class KStandardItem;
-
-/**
- * @brief Model counterpart for KStandardItemListView.
- *
- * Allows to add items to the model in an easy way by the
- * class KStandardItem.
- *
- * @see KStandardItem
- */
-class DOLPHIN_EXPORT KStandardItemModel : public KItemModelBase
-{
-    Q_OBJECT
-
-public:
-    explicit KStandardItemModel(QObject* parent = nullptr);
-    ~KStandardItemModel() override;
-
-    /**
-     * Inserts the item \a item at the index \a index. If the index
-     * is equal to the number of items of the model, the item
-     * gets appended as last element. KStandardItemModel takes
-     * the ownership of the item. If the index is invalid, the item
-     * gets deleted.
-     */
-    void insertItem(int index, KStandardItem* item);
-
-    /**
-     * Changes the item on the index \a index to \a item.
-     * KStandardItemModel takes the ownership of the item. The
-     * old item gets deleted. If the index is invalid, the item
-     * gets deleted.
-     */
-    void changeItem(int index, KStandardItem* item);
-
-    void removeItem(int index);
-    KStandardItem* item(int index) const;
-    int index(const KStandardItem* item) const;
-
-    /**
-     * Convenience method for insertItem(count(), item).
-     */
-    void appendItem(KStandardItem* item);
-
-    int count() const override;
-    QHash<QByteArray, QVariant> data(int index) const override;
-    bool setData(int index, const QHash<QByteArray, QVariant>& values) override;
-    QMimeData* createMimeData(const KItemSet& indexes) const override;
-    int indexForKeyboardSearch(const QString& text, int startFromIndex = 0) const override;
-    bool supportsDropping(int index) const override;
-    QString roleDescription(const QByteArray& role) const override;
-    QList<QPair<int, QVariant> > groups() const override;
-
-    virtual void clear();
-protected:
-    /**
-     * Is invoked after an item has been inserted and before the signal
-     * itemsInserted() gets emitted.
-     */
-    virtual void onItemInserted(int index);
-
-    /**
-     * Is invoked after an item or one of its roles has been changed and
-     * before the signal itemsChanged() gets emitted.
-     */
-    virtual void onItemChanged(int index, const QSet<QByteArray>& changedRoles);
-
-    /**
-     * Is invoked after an item has been removed and before the signal
-     * itemsRemoved() gets emitted. The item \a removedItem has already
-     * been removed from the model and will get deleted after the
-     * execution of onItemRemoved().
-     */
-    virtual void onItemRemoved(int index, KStandardItem* removedItem);
-
-private:
-    QList<KStandardItem*> m_items;
-    QHash<const KStandardItem*, int> m_indexesForItems;
-
-    friend class KStandardItem;
-    friend class KStandardItemModelTest;  // For unit testing
-};
-
-#endif
-
-
index 5c87de7129f9570d76fca0a8b8ac3e2faddedc49..4c231e2ff92ddf45f320c8359068057c19b526f6 100644 (file)
@@ -13,6 +13,7 @@
 
 #include <QCollator>
 #include <QDebug>
+#include <QSize>
 #include <QTime>
 
 namespace {
@@ -117,6 +118,18 @@ QHash<QByteArray, QVariant> KBalooRolesProvider::roleValues(const Baloo::File& f
         rangeBegin = rangeEnd;
     }
 
+    if (roles.contains("dimensions")) {
+        bool widthOk = false;
+        bool heightOk = false;
+
+        const int width = propMap.value(KFileMetaData::Property::Width).toInt(&widthOk);
+        const int height = propMap.value(KFileMetaData::Property::Height).toInt(&heightOk);
+
+        if (widthOk && heightOk && width >= 0 && height >= 0) {
+            values.insert("dimensions", QSize(width, height));
+        }
+    }
+
     KFileMetaData::UserMetaData::Attributes attributes;
     if (roles.contains("tags")) {
         attributes |= KFileMetaData::UserMetaData::Tags;
@@ -160,6 +173,7 @@ KBalooRolesProvider::KBalooRolesProvider()
     for (const auto& role : propertyRoleMap()) {
         m_roles.insert(role);
     }
+    m_roles.insert("dimensions");
 
     // Display roles provided by UserMetaData
     m_roles.insert(QByteArrayLiteral("tags"));
index e5cbc602fd9d16fbc690a30cf3428700c4271f4f..5fb929e5209b50bf48b696e68df93b08dd46978a 100644 (file)
@@ -18,6 +18,7 @@ KItemListHeaderWidget::KItemListHeaderWidget(QGraphicsWidget* parent) :
     m_automaticColumnResizing(true),
     m_model(nullptr),
     m_offset(0),
+    m_leadingPadding(0),
     m_columns(),
     m_columnWidths(),
     m_preferredColumnWidths(),
@@ -134,6 +135,20 @@ qreal KItemListHeaderWidget::offset() const
     return m_offset;
 }
 
+void KItemListHeaderWidget::setLeadingPadding(qreal width)
+{
+    if (m_leadingPadding != width) {
+        m_leadingPadding = width;
+        leadingPaddingChanged(width);
+        update();
+    }
+}
+
+qreal KItemListHeaderWidget::leadingPadding() const
+{
+    return m_leadingPadding;
+}
+
 qreal KItemListHeaderWidget::minimumColumnWidth() const
 {
     QFontMetricsF fontMetrics(font());
@@ -153,7 +168,7 @@ void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsI
     painter->setFont(font());
     painter->setPen(palette().text().color());
 
-    qreal x = -m_offset;
+    qreal x = -m_offset + m_leadingPadding;
     int orderIndex = 0;
     for (const QByteArray& role : qAsConst(m_columns)) {
         const qreal roleWidth = m_columnWidths.value(role);
@@ -172,10 +187,14 @@ void KItemListHeaderWidget::paint(QPainter* painter, const QStyleOptionGraphicsI
 void KItemListHeaderWidget::mousePressEvent(QGraphicsSceneMouseEvent* event)
 {
     if (event->button() & Qt::LeftButton) {
-        updatePressedRoleIndex(event->pos());
         m_pressedMousePos = event->pos();
-        m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ?
-                          ResizeRoleOperation : NoRoleOperation;
+        if (isAbovePaddingGrip(m_pressedMousePos, PaddingGrip::Leading)) {
+            m_roleOperation = ResizeLeadingColumnOperation;
+        } else {
+            updatePressedRoleIndex(event->pos());
+            m_roleOperation = isAboveRoleGrip(m_pressedMousePos, m_pressedRoleIndex) ?
+                              ResizeRoleOperation : NoRoleOperation;
+        }
         event->accept();
     } else {
         event->ignore();
@@ -251,7 +270,7 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
     case NoRoleOperation:
         if ((event->pos() - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
             // A role gets dragged by the user. Create a pixmap of the role that will get
-            // synchronized on each furter mouse-move-event with the mouse-position.
+            // synchronized on each further mouse-move-event with the mouse-position.
             m_roleOperation = MoveRoleOperation;
             const int roleIndex = roleIndexAt(m_pressedMousePos);
             m_movingRole.index = roleIndex;
@@ -263,7 +282,7 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
             } else {
                 m_movingRole.pixmap = createRolePixmap(roleIndex);
 
-                qreal roleX = -m_offset;
+                qreal roleX = -m_offset + m_leadingPadding;
                 for (int i = 0; i < roleIndex; ++i) {
                     const QByteArray role = m_columns[i];
                     roleX += m_columnWidths.value(role);
@@ -291,6 +310,20 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
         break;
     }
 
+    case ResizeLeadingColumnOperation: {
+        qreal currentWidth = m_leadingPadding;
+        currentWidth += event->pos().x() - event->lastPos().x();
+        currentWidth = qMax(0.0, currentWidth);
+
+        m_leadingPadding = currentWidth;
+
+        update();
+
+        Q_EMIT leadingPaddingChanged(currentWidth);
+
+        break;
+    }
+
     case MoveRoleOperation: {
         // TODO: It should be configurable whether moving the first role is allowed.
         // In the context of Dolphin this is not required, however this should be
@@ -355,7 +388,9 @@ void KItemListHeaderWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
 
     const QPointF& pos = event->pos();
     updateHoveredRoleIndex(pos);
-    if (m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) {
+    if ((m_hoveredRoleIndex >= 0 && isAboveRoleGrip(pos, m_hoveredRoleIndex)) ||
+            isAbovePaddingGrip(pos, PaddingGrip::Leading) ||
+        isAbovePaddingGrip(pos, PaddingGrip::Trailing)) {
         setCursor(Qt::SplitHCursor);
     } else {
         unsetCursor();
@@ -385,6 +420,12 @@ void KItemListHeaderWidget::paintRole(QPainter* painter,
     // The following code is based on the code from QHeaderView::paintSection().
     // SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies).
     QStyleOptionHeader option;
+    option.direction = widget->layoutDirection();
+    option.textAlignment =
+        widget->layoutDirection() == Qt::LeftToRight
+            ? Qt::AlignLeft
+            : Qt::AlignRight;
+
     option.section = orderIndex;
     option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
     if (isEnabled()) {
@@ -404,19 +445,39 @@ void KItemListHeaderWidget::paintRole(QPainter* painter,
                                 QStyleOptionHeader::SortDown : QStyleOptionHeader::SortUp;
     }
     option.rect = rect.toRect();
+    option.orientation = Qt::Horizontal;
+    option.selectedPosition = QStyleOptionHeader::NotAdjacent;
+    option.text = m_model->roleDescription(role);
 
-    bool paintBackgroundForEmptyArea = false;
+    // First we paint any potential empty (padding) space on left and/or right of this role's column.
+    const auto paintPadding = [&](int section, const QRectF &rect, const QStyleOptionHeader::SectionPosition &pos){
+        QStyleOptionHeader padding;
+        padding.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
+        padding.section = section;
+        padding.sortIndicator = QStyleOptionHeader::None;
+        padding.rect = rect.toRect();
+        padding.position = pos;
+        padding.text = QString();
+        style()->drawControl(QStyle::CE_Header, &padding, painter, widget);
+    };
 
     if (m_columns.count() == 1) {
-        option.position = QStyleOptionHeader::OnlyOneSection;
+        option.position = QStyleOptionHeader::Middle;
+        paintPadding(0, QRectF(0.0, 0.0, rect.left(), rect.height()), QStyleOptionHeader::Beginning);
+        paintPadding(1, QRectF(rect.left(), 0.0, size().width() - rect.left(), rect.height()), QStyleOptionHeader::End);
     } else if (orderIndex == 0) {
-        option.position = QStyleOptionHeader::Beginning;
+        // Paint the header for the first column; check if there is some empty space to the left which needs to be filled.
+        if (rect.left() > 0) {
+            option.position = QStyleOptionHeader::Middle;
+            paintPadding(0,QRectF(0.0, 0.0, rect.left(), rect.height()), QStyleOptionHeader::Beginning);
+        } else {
+            option.position = QStyleOptionHeader::Beginning;
+        }
     } else if (orderIndex == m_columns.count() - 1) {
-        // We are just painting the header for the last column. Check if there
-        // is some empty space to the right which needs to be filled.
+        // Paint the header for the last column; check if there is some empty space to the right which needs to be filled.
         if (rect.right() < size().width()) {
             option.position = QStyleOptionHeader::Middle;
-            paintBackgroundForEmptyArea = true;
+            paintPadding(m_columns.count(), QRectF(rect.left(), 0.0, size().width() - rect.left(), rect.height()), QStyleOptionHeader::End);
         } else {
             option.position = QStyleOptionHeader::End;
         }
@@ -424,25 +485,7 @@ void KItemListHeaderWidget::paintRole(QPainter* painter,
         option.position = QStyleOptionHeader::Middle;
     }
 
-    option.orientation = Qt::Horizontal;
-    option.selectedPosition = QStyleOptionHeader::NotAdjacent;
-    option.text = m_model->roleDescription(role);
-
     style()->drawControl(QStyle::CE_Header, &option, painter, widget);
-
-    if (paintBackgroundForEmptyArea) {
-        option.state = QStyle::State_None | QStyle::State_Raised | QStyle::State_Horizontal;
-        option.section = m_columns.count();
-        option.sortIndicator = QStyleOptionHeader::None;
-
-        qreal backgroundRectX = rect.x() + rect.width();
-        QRectF backgroundRect(backgroundRectX, 0.0, size().width() - backgroundRectX, rect.height());
-        option.rect = backgroundRect.toRect();
-        option.position = QStyleOptionHeader::End;
-        option.text = QString();
-
-        style()->drawControl(QStyle::CE_Header, &option, painter, widget);
-    }
 }
 
 void KItemListHeaderWidget::updatePressedRoleIndex(const QPointF& pos)
@@ -467,7 +510,7 @@ int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const
 {
     int index = -1;
 
-    qreal x = -m_offset;
+    qreal x = -m_offset + m_leadingPadding;
     for (const QByteArray& role : qAsConst(m_columns)) {
         ++index;
         x += m_columnWidths.value(role);
@@ -481,7 +524,7 @@ int KItemListHeaderWidget::roleIndexAt(const QPointF& pos) const
 
 bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) const
 {
-    qreal x = -m_offset;
+    qreal x = -m_offset + m_leadingPadding;
     for (int i = 0; i <= roleIndex; ++i) {
         const QByteArray role = m_columns[i];
         x += m_columnWidths.value(role);
@@ -491,6 +534,27 @@ bool KItemListHeaderWidget::isAboveRoleGrip(const QPointF& pos, int roleIndex) c
     return pos.x() >= (x - grip) && pos.x() <= x;
 }
 
+bool KItemListHeaderWidget::isAbovePaddingGrip(const QPointF& pos, PaddingGrip paddingGrip) const
+{
+    const qreal lx = -m_offset + m_leadingPadding;
+    const int grip = style()->pixelMetric(QStyle::PM_HeaderGripMargin);
+
+    switch (paddingGrip) {
+    case Leading:
+        return pos.x() >= (lx - grip) && pos.x() <= lx;
+    case Trailing:
+    {
+        qreal rx = lx;
+        for (const QByteArray& role : qAsConst(m_columns)) {
+            rx += m_columnWidths.value(role);
+        }
+        return pos.x() >= (rx - grip) && pos.x() <= rx;
+    }
+    default:
+        return false;
+    }
+}
+
 QPixmap KItemListHeaderWidget::createRolePixmap(int roleIndex) const
 {
     const QByteArray role = m_columns[roleIndex];
@@ -522,7 +586,7 @@ int KItemListHeaderWidget::targetOfMovingRole() const
     const int movingRight = movingLeft + movingWidth - 1;
 
     int targetIndex = 0;
-    qreal targetLeft = -m_offset;
+    qreal targetLeft = -m_offset + m_leadingPadding;
     while (targetIndex < m_columns.count()) {
         const QByteArray role = m_columns[targetIndex];
         const qreal targetWidth = m_columnWidths.value(role);
@@ -548,7 +612,7 @@ int KItemListHeaderWidget::targetOfMovingRole() const
 
 qreal KItemListHeaderWidget::roleXPosition(const QByteArray& role) const
 {
-    qreal x = -m_offset;
+    qreal x = -m_offset + m_leadingPadding;
     for (const QByteArray& visibleRole : qAsConst(m_columns)) {
         if (visibleRole == role) {
             return x;
index 44adc23c5b263028b43e133dc110e1074dc11c6d..58f0dc98ee4ed24f3f240d44bdc06f9795d45fdb 100644 (file)
@@ -50,6 +50,9 @@ public:
     void setOffset(qreal offset);
     qreal offset() const;
 
+    void setLeadingPadding(qreal width);
+    qreal leadingPadding() const;
+
     qreal minimumColumnWidth() const;
 
     void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
@@ -63,6 +66,8 @@ Q_SIGNALS:
                             qreal currentWidth,
                             qreal previousWidth);
 
+    void leadingPaddingChanged(qreal width);
+
     /**
      * Is emitted if the user has released the mouse button after adjusting the
      * width of a visible role.
@@ -105,6 +110,13 @@ private Q_SLOTS:
     void slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous);
 
 private:
+
+    enum PaddingGrip
+    {
+        Leading,
+        Trailing,
+    };
+
     void paintRole(QPainter* painter,
                    const QByteArray& role,
                    const QRectF& rect,
@@ -115,6 +127,7 @@ private:
     void updateHoveredRoleIndex(const QPointF& pos);
     int roleIndexAt(const QPointF& pos) const;
     bool isAboveRoleGrip(const QPointF& pos, int roleIndex) const;
+    bool isAbovePaddingGrip(const QPointF& pos, PaddingGrip paddingGrip) const;
 
     /**
      * Creates a pixmap of the role with the index \a roleIndex that is shown
@@ -138,12 +151,14 @@ private:
     {
         NoRoleOperation,
         ResizeRoleOperation,
+        ResizeLeadingColumnOperation,
         MoveRoleOperation
     };
 
     bool m_automaticColumnResizing;
     KItemModelBase* m_model;
     qreal m_offset;
+    qreal m_leadingPadding;
     QList<QByteArray> m_columns;
     QHash<QByteArray, qreal> m_columnWidths;
     QHash<QByteArray, qreal> m_preferredColumnWidths;
index 57a954adfb2c278ac5ab123bd5259ff477d8c185..0e6280edeffe8930f2238f071fd5763a0fb30f76 100644 (file)
@@ -55,7 +55,7 @@ void KItemListKeyboardSearchManager::addKeys(const QString& keys)
         const bool searchFromNextItem = (!m_isSearchRestarted && newSearch) || sameKey;
 
         // to remember not to searchFromNextItem if selection was deselected
-        // loosing keyboard search context basically
+        // losing keyboard search context basically
         m_isSearchRestarted = false;
 
         Q_EMIT changeCurrentItem(sameKey ? firstKey : m_searchedString, searchFromNextItem);
index 7deb5c6219c51d71c3f788a07050777ad7f2969c..0c2dd0b807262a800f40e8938641a98b4b709ac7 100644 (file)
@@ -29,7 +29,12 @@ QSizeF KItemListSizeHintResolver::minSizeHint()
 QSizeF KItemListSizeHintResolver::sizeHint(int index)
 {
     updateCache();
-    return QSizeF(m_logicalWidthHint, m_logicalHeightHintCache.at(index));
+    return QSizeF(m_logicalWidthHint, m_logicalHeightHintCache.at(index).first);
+}
+
+bool KItemListSizeHintResolver::isElided(int index)
+{
+    return m_logicalHeightHintCache.at(index).second;
 }
 
 void KItemListSizeHintResolver::itemsInserted(const KItemRangeList& itemRanges)
@@ -44,7 +49,7 @@ void KItemListSizeHintResolver::itemsInserted(const KItemRangeList& itemRanges)
 
     // We build the new list from the end to the beginning to mimize the
     // number of moves.
-    m_logicalHeightHintCache.insert(m_logicalHeightHintCache.end(), insertedCount, 0.0);
+    m_logicalHeightHintCache.insert(m_logicalHeightHintCache.end(), insertedCount, std::make_pair(0.0, false));
 
     int sourceIndex = currentCount - 1;
     int targetIndex = m_logicalHeightHintCache.count() - 1;
@@ -63,7 +68,7 @@ void KItemListSizeHintResolver::itemsInserted(const KItemRangeList& itemRanges)
 
         // Then: insert QSizeF() for the items which are inserted into 'range'.
         while (targetIndex >= itemsToInsertBeforeCurrentRange + range.index) {
-            m_logicalHeightHintCache[targetIndex] = 0.0;
+            m_logicalHeightHintCache[targetIndex] = std::make_pair(0.0, false);
             --targetIndex;
         }
     }
@@ -75,14 +80,14 @@ void KItemListSizeHintResolver::itemsInserted(const KItemRangeList& itemRanges)
 
 void KItemListSizeHintResolver::itemsRemoved(const KItemRangeList& itemRanges)
 {
-    const QVector<qreal>::iterator begin = m_logicalHeightHintCache.begin();
-    const QVector<qreal>::iterator end = m_logicalHeightHintCache.end();
+    const QVector<std::pair<qreal, bool>>::iterator begin = m_logicalHeightHintCache.begin();
+    const QVector<std::pair<qreal, bool>>::iterator end = m_logicalHeightHintCache.end();
 
     KItemRangeList::const_iterator rangeIt = itemRanges.constBegin();
     const KItemRangeList::const_iterator rangeEnd = itemRanges.constEnd();
 
-    QVector<qreal>::iterator destIt = begin + rangeIt->index;
-    QVector<qreal>::iterator srcIt = destIt + rangeIt->count;
+    QVector<std::pair<qreal, bool>>::iterator destIt = begin + rangeIt->index;
+    QVector<std::pair<qreal, bool>>::iterator srcIt = destIt + rangeIt->count;
 
     ++rangeIt;
 
@@ -109,7 +114,7 @@ void KItemListSizeHintResolver::itemsRemoved(const KItemRangeList& itemRanges)
 
 void KItemListSizeHintResolver::itemsMoved(const KItemRange& range, const QList<int>& movedToIndexes)
 {
-    QVector<qreal> newLogicalHeightHintCache(m_logicalHeightHintCache);
+    QVector<std::pair<qreal, bool>> newLogicalHeightHintCache(m_logicalHeightHintCache);
 
     const int movedRangeEnd = range.index + range.count;
     for (int i = range.index; i < movedRangeEnd; ++i) {
@@ -124,7 +129,7 @@ void KItemListSizeHintResolver::itemsChanged(int index, int count, const QSet<QB
 {
     Q_UNUSED(roles)
     while (count) {
-        m_logicalHeightHintCache[index] = 0.0;
+        m_logicalHeightHintCache[index] = std::make_pair(0.0, false);
         ++index;
         --count;
     }
@@ -134,7 +139,7 @@ void KItemListSizeHintResolver::itemsChanged(int index, int count, const QSet<QB
 
 void KItemListSizeHintResolver::clearCache()
 {
-    m_logicalHeightHintCache.fill(0.0);
+    m_logicalHeightHintCache.fill(std::make_pair(0.0, false));
     m_needsResolving = true;
 }
 
index 9a0ab1f5b6edc3bd607c9f6c55d248c4460ce4cb..a6cc56614e54b5d5dbcb75f3cc771f1ad98988e0 100644 (file)
@@ -25,6 +25,7 @@ public:
     virtual ~KItemListSizeHintResolver();
     QSizeF minSizeHint();
     QSizeF sizeHint(int index);
+    bool isElided(int index);
 
     void itemsInserted(const KItemRangeList& itemRanges);
     void itemsRemoved(const KItemRangeList& itemRanges);
@@ -36,7 +37,7 @@ public:
 
 private:
     const KItemListView* m_itemListView;
-    mutable QVector<qreal> m_logicalHeightHintCache;
+    mutable QVector<std::pair<qreal /* height */, bool /* isElided */>> m_logicalHeightHintCache;
     mutable qreal m_logicalWidthHint;
     mutable qreal m_minHeightHint;
     bool m_needsResolving;
index 6de83ca87eddf91897539f3e117b5617468b533f..4c22b4dbcbac26714888e4fed1adb6c82acf0fb3 100644 (file)
@@ -9,6 +9,9 @@
 #include "kitemlistsizehintresolver.h"
 #include "kitemviews/kitemmodelbase.h"
 
+#include <QGuiApplication>
+#include <QScopeGuard>
+
 // #define KITEMLISTVIEWLAYOUTER_DEBUG
 
 KItemListViewLayouter::KItemListViewLayouter(KItemListSizeHintResolver* sizeHintResolver, QObject* parent) :
@@ -343,170 +346,177 @@ void KItemListViewLayouter::markAsDirty()
 
 void KItemListViewLayouter::doLayout()
 {
-    if (m_dirty) {
+    // we always want to update visible indexes after performing a layout
+    auto qsg = qScopeGuard([this] { updateVisibleIndexes(); });
+
+    if (!m_dirty) {
+        return;
+    }
+
 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
-        QElapsedTimer timer;
-        timer.start();
+    QElapsedTimer timer;
+    timer.start();
 #endif
-        m_visibleIndexesDirty = true;
-
-        QSizeF itemSize = m_itemSize;
-        QSizeF itemMargin = m_itemMargin;
-        QSizeF size = m_size;
-
-        const bool grouped = createGroupHeaders();
-
-        const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal);
-        if (horizontalScrolling) {
-            // Flip everything so that the layout logically can work like having
-            // a vertical scrolling
-            itemSize.transpose();
-            itemMargin.transpose();
-            size.transpose();
-
-            if (grouped) {
-                // In the horizontal scrolling case all groups are aligned
-                // at the top, which decreases the available height. For the
-                // flipped data this means that the width must be decreased.
-                size.rwidth() -= m_groupHeaderHeight;
-            }
+    m_visibleIndexesDirty = true;
+
+    QSizeF itemSize = m_itemSize;
+    QSizeF itemMargin = m_itemMargin;
+    QSizeF size = m_size;
+
+    const bool grouped = createGroupHeaders();
+
+    const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal);
+    if (horizontalScrolling) {
+        // Flip everything so that the layout logically can work like having
+        // a vertical scrolling
+        itemSize.transpose();
+        itemMargin.transpose();
+        size.transpose();
+
+        if (grouped) {
+            // In the horizontal scrolling case all groups are aligned
+            // at the top, which decreases the available height. For the
+            // flipped data this means that the width must be decreased.
+            size.rwidth() -= m_groupHeaderHeight;
         }
+    }
 
-        m_columnWidth = itemSize.width() + itemMargin.width();
-        const qreal widthForColumns = size.width() - itemMargin.width();
-        m_columnCount = qMax(1, int(widthForColumns / m_columnWidth));
-        m_xPosInc = itemMargin.width();
-
-        const int itemCount = m_model->count();
-        if (itemCount > m_columnCount && m_columnWidth >= 32) {
-            // Apply the unused width equally to each column
-            const qreal unusedWidth = widthForColumns - m_columnCount * m_columnWidth;
-            if (unusedWidth > 0) {
-                const qreal columnInc = unusedWidth / (m_columnCount + 1);
-                m_columnWidth += columnInc;
-                m_xPosInc += columnInc;
-            }
+    m_columnWidth = itemSize.width() + itemMargin.width();
+    const qreal widthForColumns = size.width() - itemMargin.width();
+    m_columnCount = qMax(1, int(widthForColumns / m_columnWidth));
+    m_xPosInc = itemMargin.width();
+
+    const int itemCount = m_model->count();
+    if (itemCount > m_columnCount && m_columnWidth >= 32) {
+        // Apply the unused width equally to each column
+        const qreal unusedWidth = widthForColumns - m_columnCount * m_columnWidth;
+        if (unusedWidth > 0) {
+            const qreal columnInc = unusedWidth / (m_columnCount + 1);
+            m_columnWidth += columnInc;
+            m_xPosInc += columnInc;
         }
+    }
 
-        m_itemInfos.resize(itemCount);
+    m_itemInfos.resize(itemCount);
 
-        // Calculate the offset of each column, i.e., the x-coordinate where the column starts.
-        m_columnOffsets.resize(m_columnCount);
-        qreal currentOffset = m_xPosInc;
+    // Calculate the offset of each column, i.e., the x-coordinate where the column starts.
+    m_columnOffsets.resize(m_columnCount);
+    qreal currentOffset = QGuiApplication::isRightToLeft() ? widthForColumns : m_xPosInc;
 
-        if (grouped && horizontalScrolling) {
-            // All group headers will always be aligned on the top and not
-            // flipped like the other properties.
-            currentOffset += m_groupHeaderHeight;
-        }
+    if (grouped && horizontalScrolling) {
+        // All group headers will always be aligned on the top and not
+        // flipped like the other properties.
+        currentOffset += m_groupHeaderHeight;
+    }
 
-        for (int column = 0; column < m_columnCount; ++column) {
-            m_columnOffsets[column] = currentOffset;
-            currentOffset += m_columnWidth;
-        }
+    if (QGuiApplication::isLeftToRight()) for (int column = 0; column < m_columnCount; ++column) {
+        m_columnOffsets[column] = currentOffset;
+        currentOffset += m_columnWidth;
+    }
+    else for (int column = 0; column < m_columnCount; ++column) {
+        m_columnOffsets[column] = currentOffset - m_columnWidth;
+        currentOffset -= m_columnWidth;
+    }
 
-        // Prepare the QVector which stores the y-coordinate for each new row.
-        int numberOfRows = (itemCount + m_columnCount - 1) / m_columnCount;
-        if (grouped && m_columnCount > 1) {
-            // In the worst case, a new row will be started for every group.
-            // We could calculate the exact number of rows now to prevent that we reserve
-            // too much memory, but the code required to do that might need much more
-            // memory than it would save in the average case.
-            numberOfRows += m_groupItemIndexes.count();
-        }
-        m_rowOffsets.resize(numberOfRows);
-
-        qreal y = m_headerHeight + itemMargin.height();
-        int row = 0;
-
-        int index = 0;
-        while (index < itemCount) {
-            qreal maxItemHeight = itemSize.height();
-
-            if (grouped) {
-                if (m_groupItemIndexes.contains(index)) {
-                    // The item is the first item of a group.
-                    // Increase the y-position to provide space
-                    // for the group header.
-                    if (index > 0) {
-                        // Only add a margin if there has been added another
-                        // group already before
-                        y += m_groupHeaderMargin;
-                    } else if (!horizontalScrolling) {
-                        // The first group header should be aligned on top
-                        y -= itemMargin.height();
-                    }
-
-                    if (!horizontalScrolling) {
-                        y += m_groupHeaderHeight;
-                    }
+    // Prepare the QVector which stores the y-coordinate for each new row.
+    int numberOfRows = (itemCount + m_columnCount - 1) / m_columnCount;
+    if (grouped && m_columnCount > 1) {
+        // In the worst case, a new row will be started for every group.
+        // We could calculate the exact number of rows now to prevent that we reserve
+        // too much memory, but the code required to do that might need much more
+        // memory than it would save in the average case.
+        numberOfRows += m_groupItemIndexes.count();
+    }
+    m_rowOffsets.resize(numberOfRows);
+
+    qreal y = m_headerHeight + itemMargin.height();
+    int row = 0;
+
+    int index = 0;
+    while (index < itemCount) {
+        qreal maxItemHeight = itemSize.height();
+
+        if (grouped) {
+            if (m_groupItemIndexes.contains(index)) {
+                // The item is the first item of a group.
+                // Increase the y-position to provide space
+                // for the group header.
+                if (index > 0) {
+                    // Only add a margin if there has been added another
+                    // group already before
+                    y += m_groupHeaderMargin;
+                } else if (!horizontalScrolling) {
+                    // The first group header should be aligned on top
+                    y -= itemMargin.height();
                 }
-            }
-
-            m_rowOffsets[row] = y;
 
-            int column = 0;
-            while (index < itemCount && column < m_columnCount) {
-                qreal requiredItemHeight = itemSize.height();
-                const QSizeF sizeHint = m_sizeHintResolver->sizeHint(index);
-                const qreal sizeHintHeight = sizeHint.height();
-                if (sizeHintHeight > requiredItemHeight) {
-                    requiredItemHeight = sizeHintHeight;
+                if (!horizontalScrolling) {
+                    y += m_groupHeaderHeight;
                 }
+            }
+        }
 
-                ItemInfo& itemInfo = m_itemInfos[index];
-                itemInfo.column = column;
-                itemInfo.row = row;
-
-                if (grouped && horizontalScrolling) {
-                    // When grouping is enabled in the horizontal mode, the header alignment
-                    // looks like this:
-                    //   Header-1 Header-2 Header-3
-                    //   Item 1   Item 4   Item 7
-                    //   Item 2   Item 5   Item 8
-                    //   Item 3   Item 6   Item 9
-                    // In this case 'requiredItemHeight' represents the column-width. We don't
-                    // check the content of the header in the layouter to determine the required
-                    // width, hence assure that at least a minimal width of 15 characters is given
-                    // (in average a character requires the halve width of the font height).
-                    //
-                    // TODO: Let the group headers provide a minimum width and respect this width here
-                    const qreal headerWidth = minimumGroupHeaderWidth();
-                    if (requiredItemHeight < headerWidth) {
-                        requiredItemHeight = headerWidth;
-                    }
-                }
+        m_rowOffsets[row] = y;
 
-                maxItemHeight = qMax(maxItemHeight, requiredItemHeight);
-                ++index;
-                ++column;
+        int column = 0;
+        while (index < itemCount && column < m_columnCount) {
+            qreal requiredItemHeight = itemSize.height();
+            const QSizeF sizeHint = m_sizeHintResolver->sizeHint(index);
+            const qreal sizeHintHeight = sizeHint.height();
+            if (sizeHintHeight > requiredItemHeight) {
+                requiredItemHeight = sizeHintHeight;
+            }
 
-                if (grouped && m_groupItemIndexes.contains(index)) {
-                    // The item represents the first index of a group
-                    // and must aligned in the first column
-                    break;
+            ItemInfo& itemInfo = m_itemInfos[index];
+            itemInfo.column = column;
+            itemInfo.row = row;
+
+            if (grouped && horizontalScrolling) {
+                // When grouping is enabled in the horizontal mode, the header alignment
+                // looks like this:
+                //   Header-1 Header-2 Header-3
+                //   Item 1   Item 4   Item 7
+                //   Item 2   Item 5   Item 8
+                //   Item 3   Item 6   Item 9
+                // In this case 'requiredItemHeight' represents the column-width. We don't
+                // check the content of the header in the layouter to determine the required
+                // width, hence assure that at least a minimal width of 15 characters is given
+                // (in average a character requires the halve width of the font height).
+                //
+                // TODO: Let the group headers provide a minimum width and respect this width here
+                const qreal headerWidth = minimumGroupHeaderWidth();
+                if (requiredItemHeight < headerWidth) {
+                    requiredItemHeight = headerWidth;
                 }
             }
 
-            y += maxItemHeight + itemMargin.height();
-            ++row;
-        }
+            maxItemHeight = qMax(maxItemHeight, requiredItemHeight);
+            ++index;
+            ++column;
 
-        if (itemCount > 0) {
-            m_maximumScrollOffset = y;
-            m_maximumItemOffset = m_columnCount * m_columnWidth;
-        } else {
-            m_maximumScrollOffset = 0;
-            m_maximumItemOffset = 0;
+            if (grouped && m_groupItemIndexes.contains(index)) {
+                // The item represents the first index of a group
+                // and must aligned in the first column
+                break;
+            }
         }
 
-#ifdef KITEMLISTVIEWLAYOUTER_DEBUG
-        qCDebug(DolphinDebug) << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed();
-#endif
-        m_dirty = false;
+        y += maxItemHeight + itemMargin.height();
+        ++row;
     }
 
-    updateVisibleIndexes();
+    if (itemCount > 0) {
+        m_maximumScrollOffset = y;
+        m_maximumItemOffset = m_columnCount * m_columnWidth;
+    } else {
+        m_maximumScrollOffset = 0;
+        m_maximumItemOffset = 0;
+    }
+
+#ifdef KITEMLISTVIEWLAYOUTER_DEBUG
+    qCDebug(DolphinDebug) << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed();
+#endif
+    m_dirty = false;
 }
 
 void KItemListViewLayouter::updateVisibleIndexes()
index 779690c1c5de280c9742f2d432e0a3c395156986..7a2d42ea5cfe6843a0c1a621ed36b4e9f7eda67f 100644 (file)
@@ -44,10 +44,16 @@ int main(int argc, char **argv)
     // Prohibit using sudo or kdesu (but allow using the root user directly)
     if (getuid() == 0) {
         if (!qEnvironmentVariableIsEmpty("SUDO_USER")) {
-            std::cout << "Executing Dolphin with sudo is not possible due to unfixable security vulnerabilities." << std::endl;
+            std::cout << "Running Dolphin with sudo can cause bugs and expose you to security vulnerabilities. "
+                         "Instead use Dolphin normally and you will be prompted for elevated privileges when "
+                         "performing file operations that require them."
+                      << std::endl;
             return EXIT_FAILURE;
         } else if (!qEnvironmentVariableIsEmpty("KDESU_USER")) {
-            std::cout << "Executing Dolphin with kdesu is not possible due to unfixable security vulnerabilities." << std::endl;
+            std::cout << "Running Dolphin with kdesu can cause bugs and expose you to security vulnerabilities. "
+                         "Instead use Dolphin normally and you will be prompted for elevated privileges when "
+                         "performing file operations that require them."
+                      << std::endl;
             return EXIT_FAILURE;
         }
     }
@@ -148,7 +154,11 @@ int main(int argc, char **argv)
         QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement);
         QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement);
 
+#ifdef FLATPAK
+        KDBusService dolphinDBusService(KDBusService::NoExitOnFailure);
+#else
         KDBusService dolphinDBusService;
+#endif
         DBusInterface interface;
         interface.setAsDaemon();
         return app.exec();
index 81d1df86109b049d2b4d1c5e82af237a793b11c8..cec2c982bffe38e775048eb7dbadbb14437cebf7 100644 (file)
     <release version="21.12.0" date="2021-12-09"/>
     <release version="21.08.3" date="2021-11-04"/>
   </releases>
+  <content_rating type="oars-1.1"/>
 </component>
index 1d2f43f64979d7a2cc18fcc1005ca377aaf9426e..a967857c79df6ffd84a172c41a320a0c75d15a71 100755 (executable)
@@ -115,6 +115,30 @@ GenericName[zh_TW]=檔案管理員
 Terminal=false
 MimeType=inode/directory;
 InitialPreference=10
+Keywords=files;file management;file browsing;samba;network shares;Explorer;Finder;
+Keywords[ar]=ملف;ملفات;متصفح;سامبا;مشاركة;إدارة;إدارة ملفات;شبكة;
+Keywords[az]=fayllar;fayl idarəetməsi;fayl bələdçisi;smaba:şəbəkədə paylaşımlar;Araşdırmaq;Tapmaq;
+Keywords[ca]=fitxers;gestió de fitxers;explorar fitxers;samba;comparticions de xarxa;explorador;cercador;
+Keywords[ca@valencia]=fitxers;gestió de fitxers;explorar fitxers;samba;comparticions de xarxa;explorador;cercador;
+Keywords[en_GB]=files;file management;file browsing;samba;network shares;Explorer;Finder;
+Keywords[es]=archivos;gestión de archivos;administración de archivos;exploración de archivos;samba;recursos compartidos de red;gestor de archivos;administrador de archivos;explorador;buscador;
+Keywords[fr]=fichiers ; gestion de fichiers ; navigation parmi les fichiers ; samba ; partages sur réseau ; explorateur ; chercheur ;
+Keywords[hu]=fájlok;fájlkezelés;fájlböngészés;samba;hálózati megosztások;Explorer;Finder;
+Keywords[ia]=files; gestion de file; navigation de file;samba; partes de rete;Explorator;Trovator;
+Keywords[is]=skrár;skráastjórn;skráaskoðun;samba;netsameignir;Explorer;Finder;
+Keywords[it]=file;gestione dei file;navigazione dei file;samba;condivisioni di rete;Explorer;Finder;
+Keywords[ko]=files;file management;file browsing;samba;network shares;Explorer;Finder;파일;파일 관리;파일 관리자;탐색;탐색기;삼바;네트워크 공유;
+Keywords[nl]=bestanden;bestandsbeheer;bladeren in bestanden;samba;netwerk-shares;verkenner;zoeksysteem;
+Keywords[nn]=filer;filhandsaming;filutforsking;samba;nettverksressursar;Explorer;Finder;
+Keywords[pl]=pliki;zarządzenie plikami;przeglądanie plików;samba;udziały sieciowe;Przeglądarka;Finder;
+Keywords[pt_BR]=arquivo;gerenciamento de arquivos;navegação de arquivos;samba;compartilhamentos de rede;explorador;localizador;
+Keywords[ru]=files;file management;file browsing;samba;network shares;Explorer;Finder;файлы,управление файлами,просмотр файлов,сетевые папки
+Keywords[sl]=datoteke;upravljanje z datotekami;brskanje po datotekah;samba;mrežni diski;Raziskovalec;Iskalec;
+Keywords[sv]=filer;filhantering;filbläddring;samba;delade nätverksresurser;Utforskare;Finder;
+Keywords[uk]=files;file management;file browsing;samba;network shares;Explorer;Finder;файли;керування файлами;навігація;самба;спільні ресурси;мережа;експлорер;провідник;файндер;
+Keywords[vi]=files;file management;file browsing;samba;network shares;Explorer;Finder;tệp;quản lí tệp;duyệt tệp;chia sẻ mạng;
+Keywords[x-test]=xxfilesxx;xxfile managementxx;xxfile browsingxx;xxsambaxx;xxnetwork sharesxx;xxExplorerxx;xxFinderxx;
+Keywords[zh_CN]=files;file management;file browsing;samba;network shares;文件;文件管理;文件浏览;共享;共享文件夹;网络共享;浏览器;访达;查找器;
 X-DBUS-ServiceName=org.kde.dolphin
 X-KDE-Shortcuts=Meta+E
 StartupWMClass=dolphin
index 19a05d2b69542fa58f49f1424f128a883f10cd05..d3d8b81f14d6ed34c8cb1e0fcd457b5cc0bbc10d 100644 (file)
@@ -189,7 +189,19 @@ void FoldersPanel::slotItemActivated(int index)
 {
     const KFileItem item = m_model->fileItem(index);
     if (!item.isNull()) {
-        Q_EMIT folderActivated(item.url());
+        const auto modifiers = QGuiApplication::keyboardModifiers();
+        // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
+        if (modifiers & Qt::ControlModifier && modifiers & Qt::ShiftModifier) {
+            Q_EMIT folderInNewActiveTab(item.url());
+        } else if (modifiers & Qt::ControlModifier) {
+            Q_EMIT folderInNewTab(item.url());
+        } else if (modifiers & Qt::ShiftModifier) {
+            // The shift modifier is not considered because it is used to expand the tree view without actually
+            // opening the folder
+            return;
+        } else {
+            Q_EMIT folderActivated(item.url());
+        }
     }
 }
 
@@ -197,7 +209,13 @@ void FoldersPanel::slotItemMiddleClicked(int index)
 {
     const KFileItem item = m_model->fileItem(index);
     if (!item.isNull()) {
-        Q_EMIT folderMiddleClicked(item.url());
+        const auto modifiers = QGuiApplication::keyboardModifiers();
+        // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
+        if (modifiers & Qt::ShiftModifier) {
+            Q_EMIT folderInNewActiveTab(item.url());
+        } else {
+            Q_EMIT folderInNewTab(item.url());
+        }
     }
 }
 
index 26c8e4cb9590d5bd60d42f9772bde0705ebb5646..3ce7870ffe1562aa8a1cee03b85c3266b066e1fa 100644 (file)
@@ -42,7 +42,8 @@ public:
 
 Q_SIGNALS:
     void folderActivated(const QUrl& url);
-    void folderMiddleClicked(const QUrl& url);
+    void folderInNewTab(const QUrl &url);
+    void folderInNewActiveTab(const QUrl &url);
     void errorMessage(const QString& error);
 
 protected:
index 98c012243752fe98562ee419e84ea784bd6db98f..a1e8d1b1d65cbf6b590625919e79ce7b5e676ba1 100644 (file)
@@ -23,9 +23,6 @@
 
 #include <Baloo/FileMetaDataWidget>
 
-#include <panels/places/placesitem.h>
-#include <panels/places/placesitemmodel.h>
-
 #include <Phonon/BackendCapabilities>
 #include <Phonon/MediaObject>
 
@@ -60,7 +57,6 @@ InformationPanelContent::InformationPanelContent(QWidget* parent) :
     m_nameLabel(nullptr),
     m_metaDataWidget(nullptr),
     m_metaDataArea(nullptr),
-    m_placesItemModel(nullptr),
     m_isVideo(false)
 {
     parent->installEventFilter(this);
@@ -151,8 +147,6 @@ InformationPanelContent::InformationPanelContent(QWidget* parent) :
     layout->addWidget(m_configureButtons);
 
     grabGesture(Qt::TapAndHoldGesture);
-
-    m_placesItemModel = new PlacesItemModel(this);
 }
 
 InformationPanelContent::~InformationPanelContent()
@@ -193,7 +187,7 @@ void InformationPanelContent::refreshPixmapView()
                                        QSize(m_preview->width(), m_preview->height()),
                                        &plugins);
     m_previewJob->setScaleType(KIO::PreviewJob::Unscaled);
-    m_previewJob->setIgnoreMaximumSize(m_item.isLocalFile());
+    m_previewJob->setIgnoreMaximumSize(m_item.isLocalFile() && !m_item.isSlow());
     if (m_previewJob->uiDelegate()) {
         KJobWidgets::setWindow(m_previewJob, this);
     }
@@ -391,22 +385,25 @@ void InformationPanelContent::showPreview(const KFileItem& item,
     KIconLoader::global()->drawOverlays(item.overlays(), p, KIconLoader::Desktop);
 
     if (m_isVideo) {
-        // adds a play arrow
+        // adds a play arrow overlay
+
+        auto maxDim = qMax(p.width(), p.height());
+        auto arrowSize = qMax(PLAY_ARROW_SIZE, maxDim / 8);
 
         // compute relative pixel positions
-        const int zeroX = static_cast<int>((p.width() / 2 - PLAY_ARROW_SIZE / 2) / pixmap.devicePixelRatio());
-        const int zeroY = static_cast<int>((p.height() / 2 - PLAY_ARROW_SIZE / 2) / pixmap.devicePixelRatio());
+        const int zeroX = static_cast<int>((p.width() / 2 - arrowSize / 2) / pixmap.devicePixelRatio());
+        const int zeroY = static_cast<int>((p.height() / 2 - arrowSize / 2) / pixmap.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);
+        arrow << QPoint(zeroX, zeroY + arrowSize);
+        arrow << QPoint(zeroX + arrowSize, zeroY + arrowSize / 2);
 
         QPainterPath path;
         path.addPolygon(arrow);
 
-        QLinearGradient gradient(QPointF(zeroX, zeroY),
-                                 QPointF(zeroX + PLAY_ARROW_SIZE,zeroY + PLAY_ARROW_SIZE));
+        QLinearGradient gradient(QPointF(zeroX, zeroY + arrowSize / 2),
+                                 QPointF(zeroX + arrowSize, zeroY + arrowSize / 2));
 
         QColor whiteColor = Qt::white;
         QColor blackColor = Qt::black;
index 78fcf3cd08c6b2e83cc783ae2f196c98cfd4c713..38383bd41c41ec64f5699edad8097de621becbec 100644 (file)
@@ -17,7 +17,6 @@
 class KFileItemList;
 class PhononWidget;
 class PixmapViewer;
-class PlacesItemModel;
 class QPixmap;
 class QDialogButtonBox;
 class QString;
@@ -151,7 +150,6 @@ private:
     QLabel* m_configureLabel;
     QDialogButtonBox* m_configureButtons;
 
-    PlacesItemModel* m_placesItemModel;
     bool m_isVideo;
 };
 
index db0ac9495b2fe17e5d17220d3ab18ebbc2290e66..58f77a959716a86ab36bc61494afb63819b7b74a 100644 (file)
@@ -8,7 +8,7 @@
     <kcfgfile name="dolphinrc"/>
     <group name="PlacesPanel">
         <entry name="IconSize" type="Int">
-            <label>Size of icons in the Places Panel (-1 means "use the style's small size")</label>
+            <label>Size of icons in the Places Panel (-1 means "automatic")</label>
             <default code="true">KIconLoader::SizeSmallMedium</default>
         </entry>
     </group>
diff --git a/src/panels/places/placesitem.cpp b/src/panels/places/placesitem.cpp
deleted file mode 100644 (file)
index 9cac01f..0000000
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- * SPDX-FileCopyrightText: 2018 Elvis Angelaccio <elvis.angelaccio@kde.org>
- *
- * Based on KFilePlacesItem from kdelibs:
- * SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "placesitem.h"
-#include "trash/dolphintrash.h"
-
-#include "dolphindebug.h"
-#include "placesitemsignalhandler.h"
-
-#include <KDirLister>
-#include <KLocalizedString>
-#include <Solid/Block>
-
-PlacesItem::PlacesItem(const KBookmark& bookmark, PlacesItem* parent) :
-    KStandardItem(parent),
-    m_device(),
-    m_access(),
-    m_volume(),
-    m_disc(),
-    m_player(),
-    m_signalHandler(nullptr),
-    m_bookmark()
-{
-    m_signalHandler = new PlacesItemSignalHandler(this);
-    setBookmark(bookmark);
-}
-
-PlacesItem::~PlacesItem()
-{
-    delete m_signalHandler;
-}
-
-void PlacesItem::setUrl(const QUrl &url)
-{
-    // The default check in KStandardItem::setDataValue()
-    // for equal values does not work with a custom value
-    // like QUrl. Hence do a manual check to prevent that
-    // setting an equal URL results in an itemsChanged()
-    // signal.
-    if (dataValue("url").toUrl() != url) {
-        if (url.scheme() == QLatin1String("trash")) {
-            QObject::connect(&Trash::instance(), &Trash::emptinessChanged, m_signalHandler.data(), &PlacesItemSignalHandler::onTrashEmptinessChanged);
-        }
-
-        setDataValue("url", url);
-    }
-}
-
-QUrl PlacesItem::url() const
-{
-    return dataValue("url").toUrl();
-}
-
-void PlacesItem::setUdi(const QString& udi)
-{
-    setDataValue("udi", udi);
-}
-
-QString PlacesItem::udi() const
-{
-    return dataValue("udi").toString();
-}
-
-void PlacesItem::setApplicationName(const QString &applicationName)
-{
-    setDataValue("applicationName", applicationName);
-}
-
-QString PlacesItem::applicationName() const
-{
-    return dataValue("applicationName").toString();
-}
-
-void PlacesItem::setHidden(bool hidden)
-{
-    setDataValue("isHidden", hidden);
-}
-
-bool PlacesItem::isHidden() const
-{
-    return dataValue("isHidden").toBool();
-}
-
-bool PlacesItem::isGroupHidden() const
-{
-    return dataValue("isGroupHidden").toBool();
-}
-
-void PlacesItem::setGroupHidden(bool hidden)
-{
-    setDataValue("isGroupHidden", hidden);
-}
-
-void PlacesItem::setSystemItem(bool isSystemItem)
-{
-    setDataValue("isSystemItem", isSystemItem);
-}
-
-bool PlacesItem::isSystemItem() const
-{
-    return dataValue("isSystemItem").toBool();
-}
-
-Solid::Device PlacesItem::device() const
-{
-    return m_device;
-}
-
-void PlacesItem::setBookmark(const KBookmark& bookmark)
-{
-    const bool bookmarkDataChanged = !(bookmark == m_bookmark);
-
-    // bookmark object must be updated to keep in sync with source model
-    m_bookmark = bookmark;
-
-    if (!bookmarkDataChanged) {
-        return;
-    }
-
-    delete m_access;
-    delete m_volume;
-    delete m_disc;
-    delete m_player;
-
-    const QString udi = bookmark.metaDataItem(QStringLiteral("UDI"));
-    if (udi.isEmpty()) {
-        setIcon(bookmark.icon());
-        setText(i18ndc("kio5", "KFile System Bookmarks", bookmark.text().toUtf8().constData()));
-        setUrl(bookmark.url());
-        setSystemItem(bookmark.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true"));
-    } else {
-        initializeDevice(udi);
-    }
-
-    setHidden(bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true"));
-}
-
-KBookmark PlacesItem::bookmark() const
-{
-    return m_bookmark;
-}
-
-bool PlacesItem::storageSetupNeeded() const
-{
-    return m_access ? !m_access->isAccessible() : false;
-}
-
-bool PlacesItem::isSearchOrTimelineUrl() const
-{
-    const QString urlScheme = url().scheme();
-    return (urlScheme.contains("search") || urlScheme.contains("timeline"));
-}
-
-void PlacesItem::onDataValueChanged(const QByteArray& role,
-                                    const QVariant& current,
-                                    const QVariant& previous)
-{
-    Q_UNUSED(current)
-    Q_UNUSED(previous)
-
-    if (!m_bookmark.isNull()) {
-        updateBookmarkForRole(role);
-    }
-}
-
-void PlacesItem::onDataChanged(const QHash<QByteArray, QVariant>& current,
-                               const QHash<QByteArray, QVariant>& previous)
-{
-    Q_UNUSED(previous)
-
-    if (!m_bookmark.isNull()) {
-        QHashIterator<QByteArray, QVariant> it(current);
-        while (it.hasNext()) {
-            it.next();
-            updateBookmarkForRole(it.key());
-        }
-    }
-}
-
-void PlacesItem::initializeDevice(const QString& udi)
-{
-    m_device = Solid::Device(udi);
-    if (!m_device.isValid()) {
-        return;
-    }
-
-    m_access = m_device.as<Solid::StorageAccess>();
-    m_volume = m_device.as<Solid::StorageVolume>();
-    m_disc = m_device.as<Solid::OpticalDisc>();
-    m_player = m_device.as<Solid::PortableMediaPlayer>();
-
-    setText(m_device.displayName());
-    setIcon(m_device.icon());
-    setIconOverlays(m_device.emblems());
-    setUdi(udi);
-
-    if (m_access) {
-        setUrl(QUrl::fromLocalFile(m_access->filePath()));
-        QObject::connect(m_access.data(), &Solid::StorageAccess::accessibilityChanged,
-                         m_signalHandler.data(), &PlacesItemSignalHandler::onAccessibilityChanged);
-        QObject::connect(m_access.data(), &Solid::StorageAccess::teardownRequested,
-                         m_signalHandler.data(), &PlacesItemSignalHandler::onTearDownRequested);
-    } else if (m_disc && (m_disc->availableContent() & Solid::OpticalDisc::Audio) != 0) {
-        Solid::Block *block = m_device.as<Solid::Block>();
-        if (block) {
-            const QString device = block->device();
-            setUrl(QUrl(QStringLiteral("audiocd:/?device=%1").arg(device)));
-        } else {
-            setUrl(QUrl(QStringLiteral("audiocd:/")));
-        }
-    } else if (m_player) {
-        const QStringList protocols = m_player->supportedProtocols();
-        if (!protocols.isEmpty()) {
-            setUrl(QUrl(QStringLiteral("%1:udi=%2").arg(protocols.first(), m_device.udi())));
-        }
-    }
-}
-
-void PlacesItem::onAccessibilityChanged()
-{
-    setIconOverlays(m_device.emblems());
-    setUrl(QUrl::fromLocalFile(m_access->filePath()));
-}
-
-void PlacesItem::updateBookmarkForRole(const QByteArray& role)
-{
-    Q_ASSERT(!m_bookmark.isNull());
-    if (role == "iconName") {
-        m_bookmark.setIcon(icon());
-    } else if (role == "text") {
-        // Only store the text in the KBookmark if it is not the translation of
-        // the current text. This makes sure that the text is re-translated if
-        // the user chooses another language, or the translation itself changes.
-        //
-        // NOTE: It is important to use "KFile System Bookmarks" as context
-        // (see PlacesItemModel::createSystemBookmarks()).
-        if (text() != i18ndc("kio5", "KFile System Bookmarks", m_bookmark.text().toUtf8().data())) {
-            m_bookmark.setFullText(text());
-        }
-    } else if (role == "url") {
-        m_bookmark.setUrl(url());
-    } else if (role == "udi") {
-        m_bookmark.setMetaDataItem(QStringLiteral("UDI"), udi());
-    } else if (role == "applicationName") {
-        m_bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), applicationName());
-    } else if (role == "isSystemItem") {
-        m_bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), isSystemItem() ? QStringLiteral("true") : QStringLiteral("false"));
-    } else if (role == "isHidden") {
-        m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), isHidden() ? QStringLiteral("true") : QStringLiteral("false"));
-    }
-}
-
-QString PlacesItem::generateNewId()
-{
-    // The ID-generation must be different as done in KFilePlacesItem from kdelibs
-    // to prevent identical IDs, because 'count' is of course not shared. We append a
-    // " (V2)" to indicate that the ID has been generated by
-    // a new version of the places view.
-    static int count = 0;
-    return QString::number(QDateTime::currentDateTimeUtc().toTime_t()) +
-            '/' + QString::number(count++) + " (V2)";
-}
-
-PlacesItemSignalHandler *PlacesItem::signalHandler() const
-{
-    return m_signalHandler.data();
-}
diff --git a/src/panels/places/placesitem.h b/src/panels/places/placesitem.h
deleted file mode 100644 (file)
index 8325923..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef PLACESITEM_H
-#define PLACESITEM_H
-
-#include "kitemviews/kstandarditem.h"
-
-#include <KBookmark>
-#include <Solid/Device>
-#include <Solid/OpticalDisc>
-#include <Solid/PortableMediaPlayer>
-#include <Solid/StorageAccess>
-#include <Solid/StorageVolume>
-
-#include <QPointer>
-#include <QUrl>
-
-
-class KDirLister;
-class PlacesItemSignalHandler;
-
-/**
- * @brief Extends KStandardItem by places-specific properties.
- */
-class PlacesItem : public KStandardItem
-{
-
-public:
-    explicit PlacesItem(const KBookmark& bookmark, PlacesItem* parent = nullptr);
-    ~PlacesItem() override;
-
-    void setUrl(const QUrl& url);
-    QUrl url() const;
-
-    void setUdi(const QString& udi);
-    QString udi() const;
-
-    void setApplicationName(const QString& applicationName);
-    QString applicationName() const;
-
-    void setHidden(bool hidden);
-    bool isHidden() const;
-
-    void setGroupHidden(bool hidden);
-    bool isGroupHidden() const;
-
-    void setSystemItem(bool isSystemItem);
-    bool isSystemItem() const;
-
-    Solid::Device device() const;
-
-    void setBookmark(const KBookmark& bookmark);
-    KBookmark bookmark() const;
-
-    bool storageSetupNeeded() const;
-
-    bool isSearchOrTimelineUrl() const;
-
-    PlacesItemSignalHandler* signalHandler() const;
-
-protected:
-    void onDataValueChanged(const QByteArray& role,
-                                    const QVariant& current,
-                                    const QVariant& previous) override;
-
-    void onDataChanged(const QHash<QByteArray, QVariant>& current,
-                               const QHash<QByteArray, QVariant>& previous) override;
-
-private:
-    PlacesItem(const PlacesItem& item);
-
-    void initializeDevice(const QString& udi);
-
-    /**
-     * Is invoked if the accessibility of the storage access
-     * m_access has been changed and updates the emblem.
-     */
-    void onAccessibilityChanged();
-
-    /**
-     * Applies the data-value from the role to m_bookmark.
-     */
-    void updateBookmarkForRole(const QByteArray& role);
-
-    static QString generateNewId();
-
-private:
-    Solid::Device m_device;
-    QPointer<Solid::StorageAccess> m_access;
-    QPointer<Solid::StorageVolume> m_volume;
-    QPointer<Solid::OpticalDisc> m_disc;
-    QPointer<Solid::PortableMediaPlayer> m_player;
-    QPointer<PlacesItemSignalHandler> m_signalHandler;
-    KBookmark m_bookmark;
-
-    friend class PlacesItemSignalHandler; // Calls onAccessibilityChanged()
-};
-
-#endif
-
-
diff --git a/src/panels/places/placesitemlistgroupheader.cpp b/src/panels/places/placesitemlistgroupheader.cpp
deleted file mode 100644 (file)
index 76fd670..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * Based on the Itemviews NG project from Trolltech Labs
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "placesitemlistgroupheader.h"
-
-PlacesItemListGroupHeader::PlacesItemListGroupHeader(QGraphicsWidget* parent) :
-    KStandardItemListGroupHeader(parent)
-{
-}
-
-PlacesItemListGroupHeader::~PlacesItemListGroupHeader()
-{
-}
-
-void PlacesItemListGroupHeader::paintSeparator(QPainter* painter, const QColor& color)
-{
-    Q_UNUSED(painter)
-    Q_UNUSED(color)
-}
-
-QPalette::ColorRole PlacesItemListGroupHeader::normalTextColorRole() const
-{
-    return QPalette::WindowText;
-}
-
diff --git a/src/panels/places/placesitemlistgroupheader.h b/src/panels/places/placesitemlistgroupheader.h
deleted file mode 100644 (file)
index dfb91f8..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef PLACESITEMLISTGROUPHEADER_H
-#define PLACESITEMLISTGROUPHEADER_H
-
-#include "kitemviews/kstandarditemlistgroupheader.h"
-
-class PlacesItemListGroupHeader : public KStandardItemListGroupHeader
-{
-    Q_OBJECT
-
-public:
-    explicit PlacesItemListGroupHeader(QGraphicsWidget* parent = nullptr);
-    ~PlacesItemListGroupHeader() override;
-
-protected:
-    void paintSeparator(QPainter* painter, const QColor& color) override;
-
-    QPalette::ColorRole normalTextColorRole() const override;
-};
-#endif
-
-
diff --git a/src/panels/places/placesitemlistwidget.cpp b/src/panels/places/placesitemlistwidget.cpp
deleted file mode 100644 (file)
index ba7a0c4..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "placesitemlistwidget.h"
-
-#include <QStyleOption>
-
-#include <KColorScheme>
-
-#include <Solid/Device>
-#include <Solid/NetworkShare>
-
-#define CAPACITYBAR_HEIGHT 2
-#define CAPACITYBAR_MARGIN 2
-#define CAPACITYBAR_CACHE_TTL 60000
-
-
-PlacesItemListWidget::PlacesItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) :
-    KStandardItemListWidget(informant, parent)
-    , m_drawCapacityBar(false)
-{
-}
-
-PlacesItemListWidget::~PlacesItemListWidget()
-{
-}
-
-bool PlacesItemListWidget::isHidden() const
-{
-    return data().value("isHidden").toBool() ||
-           data().value("isGroupHidden").toBool();
-}
-
-QPalette::ColorRole PlacesItemListWidget::normalTextColorRole() const
-{
-    return QPalette::WindowText;
-}
-
-void PlacesItemListWidget::updateCapacityBar()
-{
-    const QString udi = data().value("udi").toString();
-    if (udi.isEmpty()) {
-        resetCapacityBar();
-        return;
-    }
-    const Solid::Device device = Solid::Device(udi);
-    if (device.isDeviceInterface(Solid::DeviceInterface::NetworkShare)
-            || device.isDeviceInterface(Solid::DeviceInterface::OpticalDrive)
-            || device.isDeviceInterface(Solid::DeviceInterface::OpticalDisc)) {
-        resetCapacityBar();
-        return;
-    }
-    const QUrl url = data().value("url").toUrl();
-
-    if (url.isEmpty() || m_freeSpaceInfo.job || !m_freeSpaceInfo.lastUpdated.hasExpired()) {
-        // No url, job running or cache is still valid.
-        return;
-    }
-
-    m_freeSpaceInfo.job = KIO::fileSystemFreeSpace(url);
-    connect(
-        m_freeSpaceInfo.job,
-        &KIO::FileSystemFreeSpaceJob::result,
-        this,
-        [this](KIO::Job *job, KIO::filesize_t size, KIO::filesize_t available) {
-            // even if we receive an error we want to refresh lastUpdated to avoid repeatedly querying in this case
-            m_freeSpaceInfo.lastUpdated.setRemainingTime(CAPACITYBAR_CACHE_TTL);
-
-            if (job->error()) {
-                return;
-            }
-
-            m_freeSpaceInfo.size = size;
-            m_freeSpaceInfo.used = size - available;
-            m_freeSpaceInfo.usedRatio = (qreal)m_freeSpaceInfo.used / (qreal)m_freeSpaceInfo.size;
-            m_drawCapacityBar = size > 0;
-
-            update();
-        }
-    );
-}
-
-void PlacesItemListWidget::resetCapacityBar()
-{
-    m_drawCapacityBar = false;
-    delete m_freeSpaceInfo.job;
-    m_freeSpaceInfo.lastUpdated.setRemainingTime(0);
-    m_freeSpaceInfo.size = 0;
-    m_freeSpaceInfo.used = 0;
-    m_freeSpaceInfo.usedRatio = 0;
-}
-
-void PlacesItemListWidget::polishEvent()
-{
-    updateCapacityBar();
-
-    QGraphicsWidget::polishEvent();
-}
-
-void PlacesItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
-{
-    KStandardItemListWidget::paint(painter, option, widget);
-
-    // We check if option=nullptr since it is null when the place is dragged (Bug #430441)
-    if (m_drawCapacityBar && option) {
-        const TextInfo* textInfo = m_textInfo.value("text");
-        if (textInfo) { // See KStandarItemListWidget::paint() for info on why we check textInfo.
-            painter->save();
-
-            const QRect capacityRect(
-                textInfo->pos.x(),
-                option->rect.top() + option->rect.height() - CAPACITYBAR_HEIGHT - CAPACITYBAR_MARGIN,
-                qMin((qreal)option->rect.width(), selectionRect().width()) - (textInfo->pos.x() - option->rect.left()),
-                CAPACITYBAR_HEIGHT
-            );
-
-            const QPalette pal = palette();
-            const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive;
-
-            // Background
-            const QColor bgColor = isSelected()
-                ? pal.color(group, QPalette::Highlight).darker(180)
-                : pal.color(group, QPalette::Window).darker(120);
-
-            painter->fillRect(capacityRect, bgColor);
-
-            // Fill
-            const QRect fillRect(capacityRect.x(), capacityRect.y(), capacityRect.width() * m_freeSpaceInfo.usedRatio, capacityRect.height());
-            if (m_freeSpaceInfo.usedRatio >= 0.95) { // More than 95% full!
-                const QColor dangerUsedColor = KColorScheme(group, KColorScheme::View).foreground(KColorScheme::NegativeText).color();
-                painter->fillRect(fillRect, dangerUsedColor);
-            } else {
-                const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : QPalette::Highlight;
-                const QColor normalUsedColor = styleOption().palette.color(group, role);
-                painter->fillRect(fillRect, normalUsedColor);
-            }
-
-            painter->restore();
-        }
-    }
-
-    updateCapacityBar();
-}
diff --git a/src/panels/places/placesitemlistwidget.h b/src/panels/places/placesitemlistwidget.h
deleted file mode 100644 (file)
index 9c8272f..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef PLACESITEMLISTWIDGET_H
-#define PLACESITEMLISTWIDGET_H
-
-#include "kitemviews/kstandarditemlistwidget.h"
-
-#include <QDeadlineTimer>
-#include <QPainter>
-#include <QPointer>
-#include <QStyleOptionGraphicsItem>
-#include <QWidget>
-
-#include <KIO/FileSystemFreeSpaceJob>
-
-
-// The free space / capacity bar is based on KFilePlacesView.
-// https://invent.kde.org/frameworks/kio/-/commit/933887dc334f3498505af7a86d25db7faae91019
-struct PlaceFreeSpaceInfo
-{
-    QDeadlineTimer lastUpdated;
-    KIO::filesize_t used = 0;
-    KIO::filesize_t size = 0;
-    qreal usedRatio = 0;
-    QPointer<KIO::FileSystemFreeSpaceJob> job;
-};
-
-
-/**
- * @brief Extends KStandardItemListWidget to interpret the hidden
- *        property of the PlacesModel and use the right text color.
-*/
-class PlacesItemListWidget : public KStandardItemListWidget
-{
-    Q_OBJECT
-
-public:
-    PlacesItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent);
-    ~PlacesItemListWidget() override;
-
-    void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
-    void polishEvent() override;
-
-protected:
-    bool isHidden() const override;
-    QPalette::ColorRole normalTextColorRole() const override;
-    void updateCapacityBar();
-    void resetCapacityBar();
-
-private:
-    bool m_drawCapacityBar;
-    PlaceFreeSpaceInfo m_freeSpaceInfo;
-};
-
-#endif
-
-
diff --git a/src/panels/places/placesitemmodel.cpp b/src/panels/places/placesitemmodel.cpp
deleted file mode 100644 (file)
index 3da6f7e..0000000
+++ /dev/null
@@ -1,784 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * Based on KFilePlacesModel from kdelibs:
- * SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
- * SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#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 <KAboutData>
-#include <KLocalizedString>
-#include <KUrlMimeData>
-#include <Solid/DeviceNotifier>
-#include <Solid/OpticalDrive>
-#include <KCoreAddons/KProcessList>
-#include <KCoreAddons/KListOpenFilesJob>
-
-#include <QAction>
-#include <QIcon>
-#include <QMimeData>
-#include <QTimer>
-
-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<PlacesItem*>(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<QByteArray>& 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<Solid::OpticalDisc>()) {
-        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<Solid::StorageAccess>() &&
-                                  device.as<Solid::StorageAccess>()->isAccessible();
-    if (!providesTearDown) {
-        return nullptr;
-    }
-
-    Solid::StorageDrive* drive = device.as<Solid::StorageDrive>();
-    if (!drive) {
-        drive = device.parent().as<Solid::StorageDrive>();
-    }
-
-    bool hotPluggable = false;
-    bool removable = false;
-    if (drive) {
-        hotPluggable = drive->isHotpluggable();
-        removable = drive->isRemovable();
-    }
-
-    QString iconName;
-    QString text;
-    if (device.is<Solid::OpticalDisc>()) {
-        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<Solid::OpticalDrive>();
-        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);
-            Q_EMIT errorMessage(message);
-        }
-    }
-}
-
-void PlacesItemModel::requestTearDown(int index)
-{
-    const PlacesItem* item = placesItem(index);
-    if (item) {
-        Solid::StorageAccess *tmp = item->device().as<Solid::StorageAccess>();
-        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);
-            Q_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<Solid::StorageAccess>()
-                       && !m_storageSetupInProgress.contains(device.as<Solid::StorageAccess>())
-                       && !device.as<Solid::StorageAccess>()->isAccessible();
-    if (setup) {
-        Solid::StorageAccess* access = device.as<Solid::StorageAccess>();
-
-        m_storageSetupInProgress[access] = index;
-
-        connect(access, &Solid::StorageAccess::setupDone,
-                this, &PlacesItemModel::slotStorageSetupDone);
-
-        access->setup();
-    }
-}
-
-QMimeData* PlacesItemModel::createMimeData(const KItemSet& indexes) const
-{
-    QList<QUrl> 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<QUrl> 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()) {
-        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 <application>\"%2\"</application>.",
-                            "One or more files on this device are opened in following applications: <application>%2</application>.",
-                            blockingApps.count(), blockingApps.join(i18nc("separator in list of apps blocking device unmount", ", ")));
-                }
-                Q_EMIT errorMessage(errorString);
-            });
-            listOpenFilesJob->start();
-        } else {
-            Q_EMIT errorMessage(errorData.toString());
-        }
-    } else {
-        // No error; it must have been unmounted successfully
-        Q_EMIT storageTearDownSuccessful();
-    }
-    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()) {
-            Q_EMIT errorMessage(i18nc("@info", "An error occurred while accessing '%1', the system responded: %2",
-                                    item->text(),
-                                    errorData.toString()));
-        } else {
-            Q_EMIT errorMessage(i18nc("@info", "An error occurred while accessing '%1'",
-                                    item->text()));
-        }
-        Q_EMIT storageSetupDone(index, false);
-    } else {
-        Q_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<int> &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)) {
-            // 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
-
-    static const QVector<QUrl> 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"))
-    };
-
-    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;
-}
-
diff --git a/src/panels/places/placesitemmodel.h b/src/panels/places/placesitemmodel.h
deleted file mode 100644 (file)
index cd4079a..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef PLACESITEMMODEL_H
-#define PLACESITEMMODEL_H
-
-#include "kitemviews/kstandarditemmodel.h"
-
-#include <KFilePlacesModel>
-#include <Solid/Predicate>
-#include <Solid/StorageAccess>
-
-#include <QHash>
-#include <QList>
-#include <QSet>
-#include <QUrl>
-
-class KBookmark;
-class PlacesItem;
-class QAction;
-
-/**
- * @brief Model for maintaining the bookmarks of the places panel.
- *
- * It is based on KFilePlacesModel from KIO.
- */
-class PlacesItemModel: public KStandardItemModel
-{
-    Q_OBJECT
-
-public:
-    explicit PlacesItemModel(QObject* parent = nullptr);
-    ~PlacesItemModel() override;
-
-    /**
-     * @brief Create a new place entry in the bookmark file
-     * and add it to the model
-     */
-    void createPlacesItem(const QString& text, const QUrl& url, const QString& iconName = {}, const QString& appName = {});
-    void createPlacesItem(const QString& text, const QUrl& url, const QString& iconName, const QString& appName, int after);
-
-    PlacesItem* placesItem(int index) const;
-
-    /**
-     * @brief Mark an item as hidden
-     * @param index of the item to be hidden
-     */
-    void hideItem(int index);
-
-    /**
-     * If set to true, all items that are marked as hidden
-     * will be shown in the view. The items will
-     * stay marked as hidden, which is visually indicated
-     * by the view by desaturating the icon and the text.
-     */
-    void setHiddenItemsShown(bool show);
-    bool hiddenItemsShown() const;
-
-    /**
-     * @return Number of items that are marked as hidden.
-     *         Note that this does not mean that the items
-     *         are really hidden
-     *         (see PlacesItemModel::setHiddenItemsShown()).
-     */
-    int hiddenCount() const;
-
-    /**
-     * Search the item which is equal to the URL or at least
-     * is a parent URL. If there are more than one possible
-     * candidates, return the item which covers the biggest
-     * range of the URL. -1 is returned if no closest item
-     * could be found.
-     */
-    int closestItem(const QUrl& url) const;
-
-    QAction* ejectAction(int index) const;
-    QAction* teardownAction(int index) const;
-
-    void requestEject(int index);
-    void requestTearDown(int index);
-
-    bool storageSetupNeeded(int index) const;
-    void requestStorageSetup(int index);
-
-    QMimeData* createMimeData(const KItemSet& indexes) const override;
-
-    bool supportsDropping(int index) const override;
-
-    void dropMimeDataBefore(int index, const QMimeData* mimeData);
-
-    /**
-     * @return Converts the URL, which contains "virtual" URLs for system-items like
-     *         "search:/documents" into a Query-URL that will be handled by
-     *         the corresponding IO-slave. Virtual URLs for bookmarks are used to
-     *         be independent from internal format changes.
-     */
-    static QUrl convertedUrl(const QUrl& url);
-
-    void clear() override;
-
-    void proceedWithTearDown();
-
-    /**
-     * @brief Remove item from bookmark
-     *
-     * This function remove the index from bookmark file permanently
-     *
-     * @param index - the item to be removed
-     */
-    void deleteItem(int index);
-
-    /**
-    * Force a sync on the bookmarks and indicates to other applications that the
-    * state of the bookmarks has been changed.
-    */
-    void refresh();
-
-    bool isDir(int index) const override;
-
-
-    KFilePlacesModel::GroupType groupType(int row) const;
-    bool isGroupHidden(KFilePlacesModel::GroupType type) const;
-    void setGroupHidden(KFilePlacesModel::GroupType type, bool hidden);
-
-Q_SIGNALS:
-    void errorMessage(const QString& message);
-    void storageSetupDone(int index, bool success);
-    void storageTearDownRequested(const QString& mountPath);
-    void storageTearDownExternallyRequested(const QString& mountPath);
-    void storageTearDownSuccessful();
-
-protected:
-    void onItemInserted(int index) override;
-    void onItemRemoved(int index, KStandardItem* removedItem) override;
-    void onItemChanged(int index, const QSet<QByteArray>& changedRoles) override;
-
-private Q_SLOTS:
-    void slotStorageTearDownDone(Solid::ErrorType error, const QVariant& errorData);
-    void slotStorageSetupDone(Solid::ErrorType error, const QVariant& errorData, const QString& udi);
-
-    // source model control
-    void onSourceModelRowsInserted(const QModelIndex &parent, int first, int last);
-    void onSourceModelRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
-    void onSourceModelRowsAboutToBeMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row);
-    void onSourceModelRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row);
-    void onSourceModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
-    void onSourceModelGroupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden);
-
-private:
-    /**
-     * Remove bookmarks created by the previous version of dolphin that are
-     * not valid anymore
-     */
-    void cleanupBookmarks();
-
-    /**
-     * Loads the bookmarks from the bookmark-manager and creates items for
-     * the model or moves hidden items to m_bookmarkedItems.
-     */
-    void loadBookmarks();
-
-    QString internalMimeType() const;
-
-    /**
-     * @return Adjusted drop index which assures that the item is aligned
-     *         into the same group as specified by PlacesItem::groupType().
-     */
-    int groupedDropIndex(int index, const PlacesItem* item) const;
-
-    /**
-     * @return True if the bookmarks have the same identifiers. The identifier
-     *         is the unique "ID"-property in case if no UDI is set, otherwise
-     *         the UDI is used as identifier.
-     */
-    static bool equalBookmarkIdentifiers(const KBookmark& b1, const KBookmark& b2);
-
-    /**
-     * Appends the item \a item as last element of the group
-     * the item belongs to. If no item with the same group is
-     * present, the item gets appended as last element of the
-     * model. PlacesItemModel takes the ownership
-     * of the item.
-     */
-    void insertSortedItem(PlacesItem* item);
-
-    PlacesItem *itemFromBookmark(const KBookmark &bookmark) const;
-
-    void addItemFromSourceModel(const QModelIndex &index);
-    void removeItemByIndex(const QModelIndex &mapToSource);
-
-    QString bookmarkId(const KBookmark &bookmark) const;
-    void initializeDefaultViewProperties() const;
-
-    int mapFromSource(const QModelIndex &index) const;
-    QModelIndex mapToSource(int row) const;
-
-    static void updateItem(PlacesItem *item, const QModelIndex &index);
-
-private:
-    bool m_hiddenItemsShown;
-
-    Solid::StorageAccess *m_deviceToTearDown;
-
-    QHash<QObject*, int> m_storageSetupInProgress;
-
-    KFilePlacesModel *m_sourceModel;
-
-    QVector<QPersistentModelIndex> m_indexMap;
-};
-
-#endif
-
-
diff --git a/src/panels/places/placesitemsignalhandler.cpp b/src/panels/places/placesitemsignalhandler.cpp
deleted file mode 100644 (file)
index 19f16c7..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "placesitemsignalhandler.h"
-
-#include "placesitem.h"
-
-PlacesItemSignalHandler::PlacesItemSignalHandler(PlacesItem* item,
-                                                 QObject* parent) :
-    QObject(parent),
-    m_item(item)
-{
-}
-
-PlacesItemSignalHandler::~PlacesItemSignalHandler()
-{
-}
-
-void PlacesItemSignalHandler::onAccessibilityChanged()
-{
-    if (m_item) {
-        m_item->onAccessibilityChanged();
-    }
-}
-
-void PlacesItemSignalHandler::onTearDownRequested(const QString& udi)
-{
-    Q_UNUSED(udi)
-    if (m_item) {
-        Solid::StorageAccess *tmp = m_item->device().as<Solid::StorageAccess>();
-        if (tmp) {
-            Q_EMIT tearDownExternallyRequested(tmp->filePath());
-        }
-    }
-}
-
-void PlacesItemSignalHandler::onTrashEmptinessChanged(bool isTrashEmpty)
-{
-    if (m_item) {
-        m_item->setIcon(isTrashEmpty ? QStringLiteral("user-trash") : QStringLiteral("user-trash-full"));
-    }
-}
-
diff --git a/src/panels/places/placesitemsignalhandler.h b/src/panels/places/placesitemsignalhandler.h
deleted file mode 100644 (file)
index da47839..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Peter Penz <peter.penz19@gmail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef PLACESITEMSIGNALHANDLER_H
-#define PLACESITEMSIGNALHANDLER_H
-
-#include <QObject>
-
-class PlacesItem;
-
-/**
- * @brief Helper class for PlacesItem to be able to listen to signals
- *        and performing a corresponding action.
- *
- * PlacesItem is derived from KStandardItem, which is no QObject-class
- * on purpose. To be able to internally listen to signals and performing a
- * corresponding action, PlacesItemSignalHandler is used.
- *
- * E.g. if the PlacesItem wants to react on accessibility-changes of a storage-access,
- * the signal-handler can be used like this:
- * <code>
- *     QObject::connect(storageAccess, SIGNAL(accessibilityChanged(bool,QString)),
- *                      signalHandler, SLOT(onAccessibilityChanged()));
- * </code>
- *
- * The slot PlacesItemSignalHandler::onAccessibilityChanged() will call
- * the method PlacesItem::onAccessibilityChanged().
- */
-class PlacesItemSignalHandler: public QObject
-{
-    Q_OBJECT
-
-public:
-    explicit PlacesItemSignalHandler(PlacesItem* item, QObject* parent = nullptr);
-    ~PlacesItemSignalHandler() override;
-
-public Q_SLOTS:
-    /**
-     * Calls PlacesItem::onAccessibilityChanged()
-     */
-    void onAccessibilityChanged();
-
-    void onTearDownRequested(const QString& udi);
-
-    void onTrashEmptinessChanged(bool isTrashEmpty);
-
-Q_SIGNALS:
-    void tearDownExternallyRequested(const QString& udi);
-
-private:
-    PlacesItem* m_item;
-};
-
-#endif
index 83e014a82e87846eb65262214e5a3022caf1d825..5b2f040d1896acb15e9526a05563d3a1fe1259a4 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * SPDX-FileCopyrightText: 2008-2012 Peter Penz <peter.penz19@gmail.com>
+ * SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
  *
  * Based on KFilePlacesView from kdelibs:
  * SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
 
 #include "placespanel.h"
 
+#include "dolphinplacesmodelsingleton.h"
 #include "dolphin_generalsettings.h"
+#include "dolphin_placespanelsettings.h"
 #include "global.h"
-#include "kitemviews/kitemlistcontainer.h"
-#include "kitemviews/kitemlistcontroller.h"
-#include "kitemviews/kitemlistselectionmanager.h"
-#include "kitemviews/kstandarditem.h"
-#include "placesitem.h"
-#include "placesitemlistgroupheader.h"
-#include "placesitemlistwidget.h"
-#include "placesitemmodel.h"
-#include "placesview.h"
-#include "trash/dolphintrash.h"
 #include "views/draganddrophelper.h"
 #include "settings/dolphinsettingsdialog.h"
 
-#include <KFilePlaceEditDialog>
 #include <KFilePlacesModel>
 #include <KIO/DropJob>
-#include <KIO/EmptyTrashJob>
 #include <KIO/Job>
-#include <KIconLoader>
+#include <KListOpenFilesJob>
 #include <KLocalizedString>
-#include <KMountPoint>
-#include <KPropertiesDialog>
 
-#include <QActionGroup>
-#include <QGraphicsSceneDragDropEvent>
 #include <QIcon>
 #include <QMenu>
-#include <QMimeData>
-#include <QVBoxLayout>
-#include <QToolTip>
-
-PlacesPanel::PlacesPanel(QWidget* parent) :
-    Panel(parent),
-    m_controller(nullptr),
-    m_model(nullptr),
-    m_view(nullptr),
-    m_storageSetupFailedUrl(),
-    m_triggerStorageSetupButton(),
-    m_itemDropEventIndex(-1),
-    m_itemDropEventMimeData(nullptr),
-    m_itemDropEvent(nullptr),
-    m_tooltipTimer()
-{
-    m_tooltipTimer.setInterval(500);
-    m_tooltipTimer.setSingleShot(true);
-    connect(&m_tooltipTimer, &QTimer::timeout, this, &PlacesPanel::slotShowTooltip);
-}
-
-PlacesPanel::~PlacesPanel()
-{
-}
+#include <QShowEvent>
+#include <QTimer>
 
-void PlacesPanel::proceedWithTearDown()
-{
-    m_model->proceedWithTearDown();
-}
+#include <Solid/StorageAccess>
 
-bool PlacesPanel::urlChanged()
+PlacesPanel::PlacesPanel(QWidget* parent)
+    : KFilePlacesView(parent)
 {
-    if (!url().isValid() || url().scheme().contains(QLatin1String("search"))) {
-        // Skip results shown by a search, as possible identical
-        // directory names are useless without parent-path information.
-        return false;
-    }
-
-    if (m_controller) {
-        selectItem();
-    }
+    setDropOnPlaceEnabled(true);
+    connect(this, &PlacesPanel::urlsDropped,
+            this, &PlacesPanel::slotUrlsDropped);
 
-    return true;
-}
+    setAutoResizeItemsEnabled(false);
 
-void PlacesPanel::readSettings()
-{
-    if (m_controller) {
-        const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1;
-        m_controller->setAutoActivationDelay(delay);
-    }
-}
-
-void PlacesPanel::showEvent(QShowEvent* event)
-{
-    if (event->spontaneous()) {
-        Panel::showEvent(event);
-        return;
-    }
-
-    if (!m_controller) {
-        // Postpone the creating of the controller to the first show event.
-        // This assures that no performance and memory overhead is given when the folders panel is not
-        // used at all and stays invisible.
-        m_model = new PlacesItemModel(this);
-        m_model->setGroupedSorting(true);
-        connect(m_model, &PlacesItemModel::errorMessage,
-                this, &PlacesPanel::errorMessage);
-        connect(m_model, &PlacesItemModel::storageTearDownRequested,
-                this, &PlacesPanel::storageTearDownRequested);
-        connect(m_model, &PlacesItemModel::storageTearDownExternallyRequested,
-                this, &PlacesPanel::storageTearDownExternallyRequested);
-        connect(m_model, &PlacesItemModel::storageTearDownSuccessful,
-                this, &PlacesPanel::storageTearDownSuccessful);
-
-        m_view = new PlacesView();
-        m_view->setWidgetCreator(new KItemListWidgetCreator<PlacesItemListWidget>());
-        m_view->setGroupHeaderCreator(new KItemListGroupHeaderCreator<PlacesItemListGroupHeader>());
-
-        installEventFilter(this);
-
-        m_controller = new KItemListController(m_model, m_view, this);
-        m_controller->setSelectionBehavior(KItemListController::SingleSelection);
-        m_controller->setSingleClickActivationEnforced(true);
-
-        readSettings();
+    setTeardownFunction([this](const QModelIndex &index) {
+        slotTearDownRequested(index);
+    });
 
-        connect(m_controller, &KItemListController::itemActivated, this, &PlacesPanel::slotItemActivated);
-        connect(m_controller, &KItemListController::itemMiddleClicked, this, &PlacesPanel::slotItemMiddleClicked);
-        connect(m_controller, &KItemListController::itemContextMenuRequested, this, &PlacesPanel::slotItemContextMenuRequested);
-        connect(m_controller, &KItemListController::viewContextMenuRequested, this, &PlacesPanel::slotViewContextMenuRequested);
-        connect(m_controller, &KItemListController::itemDropEvent, this, &PlacesPanel::slotItemDropEvent);
-        connect(m_controller, &KItemListController::aboveItemDropEvent, this, &PlacesPanel::slotAboveItemDropEvent);
+    m_configureTrashAction = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Trash…"));
+    m_configureTrashAction->setPriority(QAction::HighPriority);
+    connect(m_configureTrashAction, &QAction::triggered, this, &PlacesPanel::slotConfigureTrash);
+    addAction(m_configureTrashAction);
 
-        KItemListContainer* container = new KItemListContainer(m_controller, this);
-        container->setEnabledFrame(false);
+    connect(this, &PlacesPanel::contextMenuAboutToShow, this, &PlacesPanel::slotContextMenuAboutToShow);
 
-        QVBoxLayout* layout = new QVBoxLayout(this);
-        layout->setContentsMargins(0, 0, 0, 0);
-        layout->addWidget(container);
+    connect(this, &PlacesPanel::iconSizeChanged, this, [this](const QSize &newSize) {
+        int iconSize = qMin(newSize.width(), newSize.height());
+        if (iconSize == 0) {
+            // Don't store 0 size, let's keep -1 for default/small/automatic
+            iconSize = -1;
+        }
+        PlacesPanelSettings* settings = PlacesPanelSettings::self();
+        settings->setIconSize(iconSize);
+        settings->save();
+    });
+}
 
-        selectItem();
-    }
+PlacesPanel::~PlacesPanel() = default;
 
-    Panel::showEvent(event);
+void PlacesPanel::setUrl(const QUrl &url)
+{
+    // KFilePlacesView::setUrl no-ops when no model is set but we only set it in showEvent()
+    // Remember the URL and set it in showEvent
+    m_url = url;
+    KFilePlacesView::setUrl(url);
 }
 
-bool PlacesPanel::eventFilter(QObject * /* obj */, QEvent *event)
+QList<QAction*> PlacesPanel::customContextMenuActions() const
 {
-    if (event->type() == QEvent::ToolTip) {
-
-        QHelpEvent *hoverEvent = reinterpret_cast<QHelpEvent *>(event);
-
-        m_hoveredIndex = m_view->itemAt(hoverEvent->pos());
-        m_hoverPos = mapToGlobal(hoverEvent->pos());
-
-        m_tooltipTimer.start();
-        return true;
-    }
-    return false;
+    return m_customContextMenuActions;
 }
 
-void PlacesPanel::slotItemActivated(int index)
+void PlacesPanel::setCustomContextMenuActions(const QList<QAction *> &actions)
 {
-    triggerItem(index, Qt::LeftButton);
+    m_customContextMenuActions = actions;
 }
 
-void PlacesPanel::slotItemMiddleClicked(int index)
+void PlacesPanel::proceedWithTearDown()
 {
-    triggerItem(index, Qt::MiddleButton);
+    Q_ASSERT(m_deviceToTearDown);
+
+    connect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone,
+            this, &PlacesPanel::slotTearDownDone);
+    m_deviceToTearDown->teardown();
 }
 
-void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos)
+void PlacesPanel::readSettings()
 {
-    PlacesItem* item = m_model->placesItem(index);
-    if (!item) {
-        return;
+    if (GeneralSettings::autoExpandFolders()) {
+        if (!m_dragActivationTimer) {
+            m_dragActivationTimer = new QTimer(this);
+            m_dragActivationTimer->setInterval(750);
+            m_dragActivationTimer->setSingleShot(true);
+            connect(m_dragActivationTimer, &QTimer::timeout,
+                    this, &PlacesPanel::slotDragActivationTimeout);
+        }
+    } else {
+        delete m_dragActivationTimer;
+        m_dragActivationTimer = nullptr;
+        m_pendingDragActivation = QPersistentModelIndex();
     }
 
-    QMenu menu(this);
-
-    QAction* emptyTrashAction = nullptr;
-    QAction* configureTrashAction = nullptr;
-    QAction* editAction = nullptr;
-    QAction* teardownAction = nullptr;
-    QAction* ejectAction = nullptr;
-    QAction* mountAction = nullptr;
-
-    const bool isDevice = !item->udi().isEmpty();
-    const bool isTrash = (item->url().scheme() == QLatin1String("trash"));
-    if (isTrash) {
-        emptyTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18nc("@action:inmenu", "Empty Trash"));
-        emptyTrashAction->setEnabled(item->icon() == QLatin1String("user-trash-full"));
-        menu.addSeparator();
-    }
+    const int iconSize = qMax(0, PlacesPanelSettings::iconSize());
+    setIconSize(QSize(iconSize, iconSize));
+}
 
-    QAction* openInNewTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@item:inmenu", "Open in New Tab"));
-    QAction* openInNewWindowAction = menu.addAction(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@item:inmenu", "Open in New Window"));
-    QAction* propertiesAction = nullptr;
-    if (item->url().isLocalFile()) {
-        propertiesAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18nc("@action:inmenu", "Properties"));
-    }
-    if (!isDevice) {
-        menu.addSeparator();
-    }
-    
-    if (isDevice) {
-        ejectAction = m_model->ejectAction(index);
-        if (ejectAction) {
-            ejectAction->setParent(&menu);
-            menu.addAction(ejectAction);
-        }
+void PlacesPanel::showEvent(QShowEvent* event)
+{
+    if (!event->spontaneous() && !model()) {
+        readSettings();
 
-        teardownAction = m_model->teardownAction(index);
-        if (teardownAction) {
-            // Disable teardown option for root and home partitions
-            bool teardownEnabled = item->url() != QUrl::fromLocalFile(QDir::rootPath());
-            if (teardownEnabled) {
-                KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(QDir::homePath());
-                if (mountPoint && item->url() == QUrl::fromLocalFile(mountPoint->mountPoint())) {
-                    teardownEnabled = false;
-                }
-            }
-            teardownAction->setEnabled(teardownEnabled);
+        auto *placesModel = DolphinPlacesModelSingleton::instance().placesModel();
+        setModel(placesModel);
 
-            teardownAction->setParent(&menu);
-            menu.addAction(teardownAction);
-        }
+        connect(placesModel, &KFilePlacesModel::errorMessage, this, &PlacesPanel::errorMessage);
 
-        if (item->storageSetupNeeded()) {
-            mountAction = menu.addAction(QIcon::fromTheme(QStringLiteral("media-mount")), i18nc("@action:inmenu", "Mount"));
-        }
+        connect(placesModel, &QAbstractItemModel::rowsInserted, this, &PlacesPanel::slotRowsInserted);
+        connect(placesModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PlacesPanel::slotRowsAboutToBeRemoved);
 
-        if (teardownAction || ejectAction || mountAction) {
-            menu.addSeparator();
+        for (int i = 0; i < model()->rowCount(); ++i) {
+            connectDeviceSignals(model()->index(i, 0, QModelIndex()));
         }
-    }
-
-    if (isTrash) {
-        configureTrashAction = menu.addAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Trash..."));
-    }
-
-    if (!isDevice) {
-        editAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-entry")), i18nc("@item:inmenu", "Edit..."));
-    }
-
-    QAction* removeAction = nullptr;
-    if (!isDevice && !item->isSystemItem()) {
-        removeAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@item:inmenu", "Remove"));
-    }
-
-    QAction* hideAction = menu.addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), i18nc("@item:inmenu", "Hide"));
-    hideAction->setCheckable(true);
-    hideAction->setChecked(item->isHidden());
-
-    buildGroupContextMenu(&menu, index);
-
-    QAction* action = menu.exec(pos.toPoint());
-    if (action) {
-        if (action == emptyTrashAction) {
-            Trash::empty(this);
-        } else if (action == configureTrashAction) {
-            DolphinSettingsDialog* settingsDialog = new DolphinSettingsDialog(item->url(), this);
-            settingsDialog->setCurrentPage(settingsDialog->trashSettings);
-            settingsDialog->setAttribute(Qt::WA_DeleteOnClose);
-            settingsDialog->show();
-        } else {
-            // The index might have changed if devices were added/removed while
-            // the context menu was open.
-            index = m_model->index(item);
-            if (index < 0) {
-                // The item is not in the model any more, probably because it was an
-                // external device that has been removed while the context menu was open.
-                return;
-            }
 
-            if (action == editAction) {
-                editEntry(index);
-            } else if (action == removeAction) {
-                m_model->deleteItem(index);
-            } else if (action == hideAction) {
-                item->setHidden(hideAction->isChecked());
-                if (!m_model->hiddenCount()) {
-                    showHiddenEntries(false);
-                }
-            } else if (action == openInNewWindowAction) {
-                Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(m_model->data(index).value("url").toUrl())}, this);
-            } else if (action == openInNewTabAction) {
-                // TriggerItem does set up the storage first and then it will
-                // emit the slotItemMiddleClicked signal, because of Qt::MiddleButton.
-                triggerItem(index, Qt::MiddleButton);
-            } else if (action == mountAction) {
-                m_model->requestStorageSetup(index);
-            } else if (action == teardownAction) {
-                m_model->requestTearDown(index);
-            } else if (action == ejectAction) {
-                m_model->requestEject(index);
-            } else if (action == propertiesAction) {
-                KPropertiesDialog* dialog = new KPropertiesDialog(item->url(), this);
-                dialog->setAttribute(Qt::WA_DeleteOnClose);
-                dialog->show();
-            }
-        }
+        setUrl(m_url);
     }
 
-    selectItem();
+    KFilePlacesView::showEvent(event);
 }
 
-void PlacesPanel::slotViewContextMenuRequested(const QPointF& pos)
+void PlacesPanel::dragMoveEvent(QDragMoveEvent *event)
 {
-    QMenu menu(this);
-
-    QAction* addAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18nc("@item:inmenu", "Add Entry..."));
-
-    QAction* showAllAction = menu.addAction(i18nc("@item:inmenu", "Show Hidden Places"));
-    showAllAction->setCheckable(true);
-    showAllAction->setChecked(m_model->hiddenItemsShown());
-    showAllAction->setIcon(QIcon::fromTheme(m_model->hiddenItemsShown() ? QStringLiteral("view-visible") : QStringLiteral("view-hidden")));
-    showAllAction->setEnabled(m_model->hiddenCount());
-
-    buildGroupContextMenu(&menu, m_controller->indexCloseToMousePressedPosition());
-
-    QMenu* iconSizeSubMenu = new QMenu(i18nc("@item:inmenu", "Icon Size"), &menu);
-
-    struct IconSizeInfo
-    {
-        int size;
-        const char* context;
-        const char* text;
-    };
-
-    const int iconSizeCount = 4;
-    static const IconSizeInfo iconSizes[iconSizeCount] = {
-        {KIconLoader::SizeSmall,        I18NC_NOOP("Small icon size", "Small (%1x%2)")},
-        {KIconLoader::SizeSmallMedium,  I18NC_NOOP("Medium icon size", "Medium (%1x%2)")},
-        {KIconLoader::SizeMedium,       I18NC_NOOP("Large icon size", "Large (%1x%2)")},
-        {KIconLoader::SizeLarge,        I18NC_NOOP("Huge icon size", "Huge (%1x%2)")}
-    };
-
-    QHash<QAction*, int> iconSizeActionMap;
-    QActionGroup* iconSizeGroup = new QActionGroup(iconSizeSubMenu);
-
-    for (int i = 0; i < iconSizeCount; ++i) {
-        const int size = iconSizes[i].size;
-        const QString text = i18nc(iconSizes[i].context, iconSizes[i].text,
-                                   size, size);
-
-        QAction* action = iconSizeSubMenu->addAction(text);
-        iconSizeActionMap.insert(action, size);
-        action->setActionGroup(iconSizeGroup);
-        action->setCheckable(true);
-        action->setChecked(m_view->iconSize() == size);
-    }
-
-    menu.addMenu(iconSizeSubMenu);
+    KFilePlacesView::dragMoveEvent(event);
 
-    menu.addSeparator();
-    const auto actions = customContextMenuActions();
-    for (QAction* action : actions) {
-        menu.addAction(action);
+    if (!m_dragActivationTimer) {
+        return;
     }
 
-    QAction* action = menu.exec(pos.toPoint());
-    if (action) {
-        if (action == addAction) {
-            addEntry();
-        } else if (action == showAllAction) {
-            showHiddenEntries(showAllAction->isChecked());
-        } else if (iconSizeActionMap.contains(action)) {
-            m_view->setIconSize(iconSizeActionMap.value(action));
-        }
+    const QModelIndex index = indexAt(event->pos());
+    if (!index.isValid()) {
+        return;
     }
 
-    selectItem();
-}
-
-QAction *PlacesPanel::buildGroupContextMenu(QMenu *menu, int index)
-{
-    if (index == -1) {
-        return nullptr;
+    QPersistentModelIndex persistentIndex(index);
+    if (!m_pendingDragActivation.isValid() || m_pendingDragActivation != persistentIndex) {
+        m_pendingDragActivation = persistentIndex;
+        m_dragActivationTimer->start();
     }
-
-    KFilePlacesModel::GroupType groupType = m_model->groupType(index);
-    QAction *hideGroupAction = menu->addAction(QIcon::fromTheme(QStringLiteral("view-hidden")), i18nc("@item:inmenu", "Hide Section '%1'", m_model->item(index)->group()));
-    hideGroupAction->setCheckable(true);
-    hideGroupAction->setChecked(m_model->isGroupHidden(groupType));
-
-    connect(hideGroupAction, &QAction::triggered, this, [this, groupType, hideGroupAction]{
-        m_model->setGroupHidden(groupType, hideGroupAction->isChecked());
-        if (!m_model->hiddenCount()) {
-            showHiddenEntries(false);
-        }
-    });
-
-    return hideGroupAction;
 }
 
-void PlacesPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
+void PlacesPanel::dragLeaveEvent(QDragLeaveEvent *event)
 {
-    if (index < 0) {
-        return;
-    }
-
-    const PlacesItem* destItem = m_model->placesItem(index);
+    KFilePlacesView::dragLeaveEvent(event);
 
-    if (destItem->isSearchOrTimelineUrl()) {
-        return;
+    if (m_dragActivationTimer) {
+        m_dragActivationTimer->stop();
+        m_pendingDragActivation = QPersistentModelIndex();
     }
+}
 
-    if (m_model->storageSetupNeeded(index)) {
-        connect(m_model, &PlacesItemModel::storageSetupDone,
-                this, &PlacesPanel::slotItemDropEventStorageSetupDone);
-
-        m_itemDropEventIndex = index;
-
-        // Make a full copy of the Mime-Data
-        m_itemDropEventMimeData = new QMimeData;
-        m_itemDropEventMimeData->setText(event->mimeData()->text());
-        m_itemDropEventMimeData->setHtml(event->mimeData()->html());
-        m_itemDropEventMimeData->setUrls(event->mimeData()->urls());
-        m_itemDropEventMimeData->setImageData(event->mimeData()->imageData());
-        m_itemDropEventMimeData->setColorData(event->mimeData()->colorData());
-
-        m_itemDropEvent = new QDropEvent(event->pos().toPoint(),
-                                         event->possibleActions(),
-                                         m_itemDropEventMimeData,
-                                         event->buttons(),
-                                         event->modifiers());
+void PlacesPanel::dropEvent(QDropEvent *event)
+{
+    KFilePlacesView::dropEvent(event);
 
-        m_model->requestStorageSetup(index);
-        return;
+    if (m_dragActivationTimer) {
+        m_dragActivationTimer->stop();
+        m_pendingDragActivation = QPersistentModelIndex();
     }
-
-    QUrl destUrl = destItem->url();
-    QDropEvent dropEvent(event->pos().toPoint(),
-                         event->possibleActions(),
-                         event->mimeData(),
-                         event->buttons(),
-                         event->modifiers());
-
-    slotUrlsDropped(destUrl, &dropEvent, this);
 }
 
-void PlacesPanel::slotItemDropEventStorageSetupDone(int index, bool success)
+void PlacesPanel::slotConfigureTrash()
 {
-    disconnect(m_model, &PlacesItemModel::storageSetupDone,
-               this, &PlacesPanel::slotItemDropEventStorageSetupDone);
-
-    if ((index == m_itemDropEventIndex) && m_itemDropEvent && m_itemDropEventMimeData) {
-        if (success) {
-            QUrl destUrl = m_model->placesItem(index)->url();
-            slotUrlsDropped(destUrl, m_itemDropEvent, this);
-        }
+    const QUrl url = currentIndex().data(KFilePlacesModel::UrlRole).toUrl();
 
-        delete m_itemDropEventMimeData;
-        delete m_itemDropEvent;
-
-        m_itemDropEventIndex = -1;
-        m_itemDropEventMimeData = nullptr;
-        m_itemDropEvent = nullptr;
-    }
+    DolphinSettingsDialog* settingsDialog = new DolphinSettingsDialog(url, this);
+    settingsDialog->setCurrentPage(settingsDialog->trashSettings);
+    settingsDialog->setAttribute(Qt::WA_DeleteOnClose);
+    settingsDialog->show();
 }
 
-void PlacesPanel::slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
+void PlacesPanel::slotDragActivationTimeout()
 {
-    m_model->dropMimeDataBefore(index, event->mimeData());
+    if (!m_pendingDragActivation.isValid()) {
+        return;
+    }
+
+    auto *placesModel = static_cast<KFilePlacesModel *>(model());
+    Q_EMIT placeActivated(KFilePlacesModel::convertedUrl(placesModel->url(m_pendingDragActivation)));
 }
 
 void PlacesPanel::slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* parent)
@@ -475,123 +203,121 @@ void PlacesPanel::slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget*
     }
 }
 
-void PlacesPanel::slotStorageSetupDone(int index, bool success)
+void PlacesPanel::slotContextMenuAboutToShow(const QModelIndex &index, QMenu *menu)
 {
-    disconnect(m_model, &PlacesItemModel::storageSetupDone,
-               this, &PlacesPanel::slotStorageSetupDone);
+    Q_UNUSED(menu);
 
-    if (m_triggerStorageSetupButton == Qt::NoButton) {
-        return;
-    }
+    auto *placesModel = static_cast<KFilePlacesModel *>(model());
+    const QUrl url = placesModel->url(index);
+    const Solid::Device device = placesModel->deviceForIndex(index);
+
+    m_configureTrashAction->setVisible(url.scheme() == QLatin1String("trash"));
 
-    if (success) {
-        Q_ASSERT(!m_model->storageSetupNeeded(index));
-        triggerItem(index, m_triggerStorageSetupButton);
-        m_triggerStorageSetupButton = Qt::NoButton;
+    // show customContextMenuActions only on the view's context menu
+    if (!url.isValid() && !device.isValid()) {
+        addActions(m_customContextMenuActions);
     } else {
-        setUrl(m_storageSetupFailedUrl);
-        m_storageSetupFailedUrl = QUrl();
+        const auto actions = this->actions();
+        for (QAction *action : actions) {
+            if (m_customContextMenuActions.contains(action)) {
+                removeAction(action);
+            }
+        }
     }
 }
 
-void PlacesPanel::slotShowTooltip()
+void PlacesPanel::slotTearDownRequested(const QModelIndex &index)
 {
-    const QUrl url = m_model->data(m_hoveredIndex).value("url").value<QUrl>();
-    const QString text = url.toDisplayString(QUrl::PreferLocalFile);
-    QToolTip::showText(m_hoverPos, text);
-}
+    auto *placesModel = static_cast<KFilePlacesModel *>(model());
 
-void PlacesPanel::addEntry()
-{
-    const int index = m_controller->selectionManager()->currentItem();
-    const QUrl url = m_model->data(index).value("url").toUrl();
-    const QString text = url.fileName().isEmpty() ? url.toDisplayString(QUrl::PreferLocalFile) : url.fileName();
-
-    QPointer<KFilePlaceEditDialog> dialog = new KFilePlaceEditDialog(true, url, text, QString(), true, false, KIconLoader::SizeMedium, this);
-    if (dialog->exec() == QDialog::Accepted) {
-        const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString();
-        m_model->createPlacesItem(dialog->label(), dialog->url(), dialog->icon(), appName);
+    Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
+    if (!storageAccess) {
+        return;
     }
 
-    delete dialog;
+    m_deviceToTearDown = storageAccess;
+
+    // disconnect the Solid::StorageAccess::teardownRequested
+    // to prevent emitting PlacesPanel::storageTearDownExternallyRequested
+    // after we have emitted PlacesPanel::storageTearDownRequested
+    disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally);
+    Q_EMIT storageTearDownRequested(storageAccess->filePath());
 }
 
-void PlacesPanel::editEntry(int index)
+void PlacesPanel::slotTearDownRequestedExternally(const QString &udi)
 {
-    QHash<QByteArray, QVariant> data = m_model->data(index);
-    const QUrl url = data.value("url").toUrl();
-    const QString text = data.value("text").toString();
-    const QString iconName = data.value("iconName").toString();
-    const bool applicationLocal = !data.value("applicationName").toString().isEmpty();
-
-    QPointer<KFilePlaceEditDialog> dialog = new KFilePlaceEditDialog(true, url, text, iconName, true, applicationLocal, KIconLoader::SizeMedium, this);
-    if (dialog->exec() == QDialog::Accepted) {
-        PlacesItem* oldItem = m_model->placesItem(index);
-        if (oldItem) {
-            const QString appName = dialog->applicationLocal() ? QCoreApplication::applicationName() : QString();
-            oldItem->setApplicationName(appName);
-            oldItem->setText(dialog->label());
-            oldItem->setUrl(dialog->url());
-            oldItem->setIcon(dialog->icon());
-            m_model->refresh();
-        }
-    }
+    Q_UNUSED(udi);
+    auto *storageAccess = static_cast<Solid::StorageAccess*>(sender());
 
-    delete dialog;
+    Q_EMIT storageTearDownExternallyRequested(storageAccess->filePath());
 }
 
-void PlacesPanel::selectItem()
+void PlacesPanel::slotTearDownDone(Solid::ErrorType error, const QVariant& errorData)
 {
-    const int index = m_model->closestItem(url());
-    KItemListSelectionManager* selectionManager = m_controller->selectionManager();
-    selectionManager->setCurrentItem(index);
-    selectionManager->clearSelection();
-
-    const QUrl closestUrl = m_model->url(index);
-    if (!closestUrl.path().isEmpty() && url() == closestUrl) {
-        selectionManager->setSelected(index);
+    if (error && errorData.isValid()) {
+        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 <application>\"%2\"</application>.",
+                            "One or more files on this device are opened in following applications: <application>%2</application>.",
+                            blockingApps.count(), blockingApps.join(i18nc("separator in list of apps blocking device unmount", ", ")));
+                }
+                Q_EMIT errorMessage(errorString);
+            });
+            listOpenFilesJob->start();
+        } else {
+            Q_EMIT errorMessage(errorData.toString());
+        }
+    } else {
+        // No error; it must have been unmounted successfully
+        Q_EMIT storageTearDownSuccessful();
     }
+    disconnect(m_deviceToTearDown, &Solid::StorageAccess::teardownDone,
+               this, &PlacesPanel::slotTearDownDone);
+    m_deviceToTearDown = nullptr;
 }
 
-void PlacesPanel::triggerItem(int index, Qt::MouseButton button)
+void PlacesPanel::slotRowsInserted(const QModelIndex &parent, int first, int last)
 {
-    const PlacesItem* item = m_model->placesItem(index);
-    if (!item) {
-        return;
+    for (int i = first; i <= last; ++i) {
+        connectDeviceSignals(model()->index(first, 0, parent));
     }
+}
 
-    if (m_model->storageSetupNeeded(index)) {
-        m_triggerStorageSetupButton = button;
-        m_storageSetupFailedUrl = url();
+void PlacesPanel::slotRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
+{
+    auto *placesModel = static_cast<KFilePlacesModel *>(model());
 
-        connect(m_model, &PlacesItemModel::storageSetupDone,
-                this, &PlacesPanel::slotStorageSetupDone);
+    for (int i = first; i <= last; ++i) {
+        const QModelIndex index = placesModel->index(i, 0, parent);
 
-        m_model->requestStorageSetup(index);
-    } else {
-        m_triggerStorageSetupButton = Qt::NoButton;
-
-        const QUrl url = m_model->data(index).value("url").toUrl();
-        if (!url.isEmpty()) {
-            if (button == Qt::MiddleButton) {
-                Q_EMIT placeMiddleClicked(KFilePlacesModel::convertedUrl(url));
-            } else {
-                Q_EMIT placeActivated(KFilePlacesModel::convertedUrl(url));
-            }
+        Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
+        if (!storageAccess) {
+            continue;
         }
+
+        disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, nullptr);
     }
 }
 
-void PlacesPanel::showHiddenEntries(bool shown)
+void PlacesPanel::connectDeviceSignals(const QModelIndex &index)
 {
-    m_model->setHiddenItemsShown(shown);
-    Q_EMIT showHiddenEntriesChanged(shown);
-}
+    auto *placesModel = static_cast<KFilePlacesModel *>(model());
 
-int PlacesPanel::hiddenListCount()
-{
-    if(!m_model) {
-        return 0;
+    Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>();
+    if (!storageAccess) {
+        return;
     }
-    return m_model->hiddenCount();
+
+    connect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally);
 }
index 39f8da36562c7732d5d9c104bbc8f143e16e7db3..570fc43be110bc265d9d775780d1eae71ca34a9b 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * SPDX-FileCopyrightText: 2008-2012 Peter Penz <peter.penz19@gmail.com>
  * SPDX-FileCopyrightText: 2010 Christian Muehlhaeuser <muesli@gmail.com>
+ * SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
 #include "panels/panel.h"
 
 #include <QUrl>
-#include <QTimer>
-
-class KItemListController;
-class PlacesItemModel;
-class PlacesView;
-class QGraphicsSceneDragDropEvent;
-class QMenu;
-class QMimeData;
+#include <KFilePlacesView>
+
+#include <Solid/SolidNamespace> // Solid::ErrorType
+
+class QTimer;
+namespace Solid
+{
+class StorageAccess;
+}
+
 /**
  * @brief Combines bookmarks and mounted devices as list.
  */
-class PlacesPanel : public Panel
+class PlacesPanel : public KFilePlacesView
 {
     Q_OBJECT
 
 public:
     explicit PlacesPanel(QWidget* parent);
     ~PlacesPanel() override;
+
+    void setUrl(const QUrl &url); // override
+
+    // for compatibility with Panel, actions that are shown
+    // on the view's context menu
+    QList<QAction*> customContextMenuActions() const;
+    void setCustomContextMenuActions(const QList<QAction*>& actions);
+
+    void requestTearDown();
     void proceedWithTearDown();
 
-    bool eventFilter(QObject *obj, QEvent *event) override;
+public Q_SLOTS:
+    void readSettings();
 
 Q_SIGNALS:
-    void placeActivated(const QUrl& url);
-    void placeMiddleClicked(const QUrl& url);
     void errorMessage(const QString& error);
     void storageTearDownRequested(const QString& mountPath);
     void storageTearDownExternallyRequested(const QString& mountPath);
-    void showHiddenEntriesChanged(bool shown);
     void storageTearDownSuccessful();
 
 protected:
-    bool urlChanged() override;
     void showEvent(QShowEvent* event) override;
-
-public Q_SLOTS:
-    void readSettings() override;
-    void showHiddenEntries(bool shown);
-    int hiddenListCount();
+    void dragMoveEvent(QDragMoveEvent *event) override;
+    void dragLeaveEvent(QDragLeaveEvent *event) override;
+    void dropEvent(QDropEvent *event) override;
 
 private Q_SLOTS:
-    void slotItemActivated(int index);
-    void slotItemMiddleClicked(int index);
-    void slotItemContextMenuRequested(int index, const QPointF& pos);
-    void slotViewContextMenuRequested(const QPointF& pos);
-    void slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event);
-    void slotItemDropEventStorageSetupDone(int index, bool success);
-    void slotAboveItemDropEvent(int index, QGraphicsSceneDragDropEvent* event);
+    void slotConfigureTrash();
+    void slotDragActivationTimeout();
     void slotUrlsDropped(const QUrl& dest, QDropEvent* event, QWidget* parent);
-    void slotStorageSetupDone(int index, bool success);
-    void slotShowTooltip();
+    void slotContextMenuAboutToShow(const QModelIndex &index, QMenu *menu);
+    void slotTearDownRequested(const QModelIndex &index);
+    void slotTearDownRequestedExternally(const QString &udi);
+    void slotTearDownDone(Solid::ErrorType error, const QVariant& errorData);
+    void slotRowsInserted(const QModelIndex &parent, int first, int last);
+    void slotRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
 
 private:
-    void addEntry();
-    void editEntry(int index);
+    void connectDeviceSignals(const QModelIndex &idx);
 
-    /**
-     * Selects the item that matches the URL set
-     * for the panel (see Panel::setUrl()).
-     */
-    void selectItem();
+    QUrl m_url; // only used for initial setUrl
+    QList<QAction*> m_customContextMenuActions;
 
-    void triggerItem(int index, Qt::MouseButton button);
+    QTimer *m_dragActivationTimer = nullptr;
+    QPersistentModelIndex m_pendingDragActivation;
 
-    QAction* buildGroupContextMenu(QMenu* menu, int index);
+    Solid::StorageAccess *m_deviceToTearDown = nullptr;
 
-private:
-    KItemListController* m_controller;
-    PlacesItemModel* m_model;
-    PlacesView* m_view;
-
-    QUrl m_storageSetupFailedUrl;
-    Qt::MouseButton m_triggerStorageSetupButton;
-
-    int m_itemDropEventIndex;
-    QMimeData* m_itemDropEventMimeData;
-    QDropEvent* m_itemDropEvent;
-    QTimer m_tooltipTimer;
-    int m_hoveredIndex;
-    QPoint m_hoverPos;
+    QAction *m_configureTrashAction;
+    QAction *m_lockPanelsAction;
 };
 
 #endif // PLACESPANEL_H
diff --git a/src/panels/places/placesview.cpp b/src/panels/places/placesview.cpp
deleted file mode 100644 (file)
index dc264e4..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Frank Reininghaus <frank78ac@googlemail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "placesview.h"
-
-#include "dolphin_placespanelsettings.h"
-#include "kitemviews/kitemlistheader.h"
-
-#include <QGraphicsSceneResizeEvent>
-
-PlacesView::PlacesView(QGraphicsWidget* parent) :
-    KStandardItemListView(parent)
-{
-    header()->setAutomaticColumnResizing(false);
-
-    const int iconSize = PlacesPanelSettings::iconSize();
-    if (iconSize >= 0) {
-        setIconSize(iconSize);
-    }
-}
-
-void PlacesView::setIconSize(int size)
-{
-    if (size != iconSize()) {
-        PlacesPanelSettings* settings = PlacesPanelSettings::self();
-        settings->setIconSize(size);
-        settings->save();
-
-        KItemListStyleOption option = styleOption();
-        option.iconSize = size;
-        setStyleOption(option);
-    }
-}
-
-int PlacesView::iconSize() const
-{
-    const KItemListStyleOption option = styleOption();
-    return option.iconSize;
-}
-
-void PlacesView::resizeEvent(QGraphicsSceneResizeEvent *event)
-{
-    KStandardItemListView::resizeEvent(event);
-
-    header()->setColumnWidth(QByteArrayLiteral("text"), event->newSize().width());
-}
diff --git a/src/panels/places/placesview.h b/src/panels/places/placesview.h
deleted file mode 100644 (file)
index 86515f5..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2012 Frank Reininghaus <frank78ac@googlemail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef PLACESVIEW_H
-#define PLACESVIEW_H
-
-#include "kitemviews/kstandarditemlistview.h"
-
-/**
- * @brief View class for the Places Panel.
- *
- * Reads the icon size from GeneralSettings::placesPanelIconSize().
- */
-class PlacesView : public KStandardItemListView
-{
-    Q_OBJECT
-
-public:
-    explicit PlacesView(QGraphicsWidget* parent = nullptr);
-
-    void setIconSize(int size);
-    int iconSize() const;
-
-protected:
-    void resizeEvent(QGraphicsSceneResizeEvent *event) override;
-};
-
-#endif
index 3af2cdcad87d355129510c53938e4084327188ef..75bfe93d011583949b39753a9d4d7e3a798cf7bf 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "terminalpanel.h"
 
+#include <KActionCollection>
 #include <KIO/DesktopExecParser>
 #include <KIO/Job>
 #include <KIO/JobUiDelegate>
 #include <KMountPoint>
 #include <KParts/ReadOnlyPart>
 #include <KPluginFactory>
-#include <KPluginLoader>
 #include <KProtocolInfo>
 #include <KShell>
+#include <KXMLGUIBuilder>
+#include <KXMLGUIFactory>
 #include <kde_terminal_interface.h>
 
 #include <QAction>
@@ -101,6 +103,21 @@ QString TerminalPanel::runningProgramName() const
     return m_terminal ? m_terminal->foregroundProcessName() : QString();
 }
 
+KActionCollection *TerminalPanel::actionCollection()
+{
+    // m_terminal is the only reference reset to nullptr in case the terminal is
+    // closed again
+    if (m_terminal && m_konsolePart && m_terminalWidget) {
+        const auto guiClients = m_konsolePart->childClients();
+        for (auto *client : guiClients) {
+            if (client->actionCollection()->associatedWidgets().contains(m_terminalWidget)) {
+                return client->actionCollection();
+            }
+        }
+    }
+    return nullptr;
+}
+
 bool TerminalPanel::hasProgramRunning() const
 {
     return m_terminal && (m_terminal->foregroundProcessId() != -1);
@@ -129,8 +146,7 @@ void TerminalPanel::showEvent(QShowEvent* event)
 
     if (!m_terminal) {
         m_clearTerminal = true;
-        KPluginLoader loader(QStringLiteral("konsolepart"));
-        KPluginFactory* factory = loader.factory();
+        KPluginFactory *factory = KPluginFactory::loadFactory(KPluginMetaData(QStringLiteral("konsolepart"))).plugin;
         m_konsolePart = factory ? (factory->create<KParts::ReadOnlyPart>(this)) : nullptr;
         if (m_konsolePart) {
             connect(m_konsolePart, &KParts::ReadOnlyPart::destroyed, this, &TerminalPanel::terminalExited);
@@ -141,6 +157,23 @@ void TerminalPanel::showEvent(QShowEvent* event)
                 m_layout->removeWidget(m_konsolePartMissingMessage);
             }
             m_terminal = qobject_cast<TerminalInterface*>(m_konsolePart);
+
+            // needed to collect the correct KonsolePart actionCollection
+            // namely the one of the single inner terminal and not the outer KonsolePart
+            if (!m_konsolePart->factory() && m_terminalWidget) {
+                if (!m_konsolePart->clientBuilder()) {
+                    m_konsolePart->setClientBuilder(new KXMLGUIBuilder(m_terminalWidget));
+                }
+
+                auto factory = new KXMLGUIFactory(m_konsolePart->clientBuilder(), this);
+                factory->addClient(m_konsolePart);
+
+                // Prevents the KXMLGui warning about removing the client
+                connect(m_terminalWidget, &QObject::destroyed, this, [factory, this] {
+                    factory->removeClient(m_konsolePart);
+                });
+            }
+
         } else if (!m_konsolePartMissingMessage) {
             const auto konsoleInstallUrl = QUrl("appstream://org.kde.konsole.desktop");
             const auto konsoleNotInstalledText = i18n("Terminal cannot be shown because Konsole is not installed. "
index dc6605da6bd03695bf7281a4d16d00c16825265a..5a2b0bb839bb61be80f7517c958e0edc108a166f 100644 (file)
@@ -7,12 +7,13 @@
 #ifndef TERMINALPANEL_H
 #define TERMINALPANEL_H
 
-#include "panels/panel.h"
 #include "kiofuse_interface.h"
+#include "panels/panel.h"
 
 #include <QQueue>
 
 class TerminalInterface;
+class KActionCollection;
 class KMessageWidget;
 class QVBoxLayout;
 class QWidget;
@@ -47,6 +48,7 @@ public:
     bool terminalHasFocus() const;
     bool hasProgramRunning() const;
     QString runningProgramName() const;
+    KActionCollection *actionCollection();
 
 public Q_SLOTS:
     void terminalExited();
index db53d595fa4ec707b83aa1aba4e02ae3f1f8c678..cc125a2d9225e13f06fd9ffdfae27b161737fe1f 100644 (file)
@@ -235,7 +235,7 @@ void DolphinFacetsWidget::initComboBox(QComboBox* combo)
     combo->setFrame(false);
     combo->setMinimumHeight(parentWidget()->height());
     combo->setCurrentIndex(0);
-    connect(combo, QOverload<int>::of(&QComboBox::activated), this, &DolphinFacetsWidget::facetChanged);
+    connect(combo, &QComboBox::activated, this, &DolphinFacetsWidget::facetChanged);
 }
 
 void DolphinFacetsWidget::updateTagsSelector()
index 9143ddcb770023038ef245b2d61d05836216c7b6..860d9f6cd5d31b935e9073b5be77cc329b0217e0 100644 (file)
@@ -9,8 +9,8 @@
 
 #include "dolphin_searchsettings.h"
 #include "dolphinfacetswidget.h"
+#include "dolphinplacesmodelsingleton.h"
 #include "dolphinquery.h"
-#include "panels/places/placesitemmodel.h"
 
 #include <KLocalizedString>
 #include <KNS3/KMoreToolsMenuFactory>
@@ -288,11 +288,8 @@ void DolphinSearchBox::slotSearchSaved()
 {
     const QUrl searchURL = urlForSearching();
     if (searchURL.isValid()) {
-        PlacesItemModel model;
         const QString label = i18n("Search for %1 in %2", text(), searchPath().fileName());
-        model.createPlacesItem(label,
-                               searchURL,
-                               QStringLiteral("folder-saved-search-symbolic"));
+        DolphinPlacesModelSingleton::instance().placesModel()->addPlace(label, searchURL, QStringLiteral("folder-saved-search-symbolic"));
     }
 }
 
index 8ebac2e12242f15dcb7efe10ebe28e4d2e7fcae8..cec1f96491d4c6039fc03e7ac0dc845c2b34896a 100644 (file)
 #include "global.h"
 
 #include <KDesktopFile>
+#include <KDesktopFileActions>
+#include <KFileUtils>
 #include <KLocalizedString>
 #include <KMessageBox>
 #include <KNS3/Button>
 #include <KPluginMetaData>
 #include <KService>
 #include <KServiceTypeTrader>
-#include <KDesktopFileActions>
 
 #include <kio_version.h>
 
@@ -272,10 +273,14 @@ void ContextMenuSettingsPage::loadServices()
     const KConfigGroup showGroup = config.group("Show");
 
     // Load generic services
-    const KService::List entries = KServiceTypeTrader::self()->query(QStringLiteral("KonqPopupMenu/Plugin"));
-    for (const KService::Ptr &service : entries) {
-        const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kservices5/" % service->entryPath());
-        const QList<KServiceAction> serviceActions = KDesktopFileActions::userDefinedServices(file, true);
+    const auto locations = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kio/servicemenus"), QStandardPaths::LocateDirectory);
+    QStringList files = KFileUtils::findAllUniqueFiles(locations);
+    const KService::List services = KServiceTypeTrader::self()->query(QStringLiteral("KonqPopupMenu/Plugin"));
+    for (const KService::Ptr &service : services) {
+        files << QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kservices5/" % service->entryPath());
+    }
+    for (const auto &file : qAsConst(files)) {
+        const QList<KServiceAction> serviceActions = KDesktopFileActions::userDefinedServices(KService(file), true);
 
         const KDesktopFile desktopFile(file);
         const QString subMenuName = desktopFile.desktopGroup().readEntry("X-KDE-Submenu");
@@ -307,7 +312,7 @@ void ContextMenuSettingsPage::loadServices()
 #endif
 
     // Load JSON-based plugins that implement the KFileItemActionPlugin interface
-    const auto jsonPlugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kfileitemaction"));
+    const auto jsonPlugins = KPluginMetaData::findPlugins(QStringLiteral("kf5/kfileitemaction"));
 
     for (const auto &jsonMetadata : jsonPlugins) {
         const QString desktopEntryName = jsonMetadata.pluginId();
@@ -328,7 +333,7 @@ void ContextMenuSettingsPage::loadVersionControlSystems()
     // Create a checkbox for each available version control plugin
     QSet<QString> loadedPlugins;
 
-    const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("dolphin/vcs"));
+    const QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("dolphin/vcs"));
     for (const auto &plugin : plugins) {
         const QString pluginName = plugin.name();
         addRow(QStringLiteral("code-class"),
@@ -338,18 +343,6 @@ void ContextMenuSettingsPage::loadVersionControlSystems()
         loadedPlugins += pluginName;
     }
 
-    const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin"));
-    for (const auto &plugin : pluginServices) {
-        const QString pluginName = plugin->name();
-        if (loadedPlugins.contains(pluginName)) {
-            continue;
-        }
-        addRow(QStringLiteral("code-class"),
-               pluginName,
-               VersionControlServicePrefix + pluginName,
-               enabledPlugins.contains(pluginName));
-    }
-
     m_sortModel->sort(Qt::DisplayRole);
 }
 
index 59d4598951f1787bb82ab62fe25941f873defe26..4374082c77874b60179bd9d716b2e5b91dc1fbff 100644 (file)
@@ -1,5 +1,5 @@
 [KNewStuff3]
-ProvidersUrl=https://download.kde.org/ocs/providers.xml
+ProvidersUrl=https://autoconfig.kde.org/ocs/providers.xml
 Categories=Dolphin Service Menus
 TargetDir=servicemenu-download
 Uncompress=never
index 46b1590794f6be249d49442f58576429c2531423..da9f839551155e0f3306418bedbbe42f7d71c2d4 100644 (file)
@@ -3,8 +3,8 @@ add_definitions(-DTRANSLATION_DOMAIN=\"dolphin_servicemenuinstaller\")
 
 add_executable(servicemenuinstaller servicemenuinstaller.cpp)
 target_link_libraries(servicemenuinstaller PRIVATE
-    Qt5::Core
-    Qt5::Gui
+    Qt${QT_MAJOR_VERSION}::Core
+    Qt${QT_MAJOR_VERSION}::Gui
     KF5::I18n
     KF5::CoreAddons
 )
index c8238f1e827df548c91915c104c0da315c6d1615..2a70cc1ff0850e5ebd394e3dc0fc9bab6144f035 100644 (file)
             <label>Position of columns</label>
             <default>0,1,2,3,4,5,6,7,8</default>
         </entry>
+        <entry name="LeadingPadding" type="UInt">
+            <label>Leading Column Padding</label>
+            <default>20</default>
+        </entry>
         <entry name="ExpandableFolders" type="Bool">
             <label>Expandable folders</label>
             <default>true</default>
index 9b286d1391a8e3a7894e2756f014db483b6ee9f1..220dd8c4279ba9c66508c558bb9b6f73aa19bac4 100644 (file)
@@ -42,7 +42,7 @@
         </entry>
         <entry name="MaximumTextLines" type="Int">
             <label>Maximum textlines (0 means unlimited)</label>
-            <default>0</default>
+            <default>3</default>
         </entry>
     </group>
 </kcfg>
index 3b7d4b26706832129eafe583e08bbb7d19fb460f..d699ef894bc342b236a478133557fcda80a7508b 100644 (file)
@@ -90,7 +90,7 @@ DolphinSettingsDialog::DolphinSettingsDialog(const QUrl& url, QWidget* parent, K
     });
     KPageWidgetItem* contextMenuSettingsFrame = addPage(contextMenuSettingsPage,
                                                         i18nc("@title:group", "Context Menu"));
-    contextMenuSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("application-menu")));
+    contextMenuSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-menu-edit")));
     connect(contextMenuSettingsPage, &ContextMenuSettingsPage::changed, this, &DolphinSettingsDialog::enableApply);
 
     // Trash
index 26b7deb88a357c61ff4f594b0b7c5753f664d577..b60ba5a0cfb4706cd294ecd6253b3dfd4673210d 100644 (file)
@@ -11,7 +11,7 @@
 #include <KIO/ThumbCreator>
 #include <KJobWidgets>
 #include <KLocalizedString>
-#include <KPluginLoader>
+#include <QPluginLoader>
 
 #include <QDialogButtonBox>
 #include <QPushButton>
@@ -25,7 +25,7 @@ ConfigurePreviewPluginDialog::ConfigurePreviewPluginDialog(const QString& plugin
     QDialog(parent)
 {
     QSharedPointer<ThumbCreator> previewPlugin;
-    const QString pluginPath = KPluginLoader::findPlugin(desktopEntryName);
+    const QString pluginPath = QPluginLoader(desktopEntryName).fileName();
     if (!pluginPath.isEmpty()) {
         newCreator create = (newCreator)QLibrary::resolve(pluginPath, "new_creator");
         if (create) {
index 564715ae315575c3e2ca0f3b6c7af5994908afff..f12338aef0f2afa4712cec402cb1547cc65f3e3f 100644 (file)
@@ -94,8 +94,8 @@ PreviewsSettingsPage::PreviewsSettingsPage(QWidget* parent) :
     loadSettings();
 
     connect(m_listView, &QListView::clicked, this, &PreviewsSettingsPage::changed);
-    connect(m_localFileSizeBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &PreviewsSettingsPage::changed);
-    connect(m_remoteFileSizeBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &PreviewsSettingsPage::changed);
+    connect(m_localFileSizeBox, &QSpinBox::valueChanged, this, &PreviewsSettingsPage::changed);
+    connect(m_remoteFileSizeBox, &QSpinBox::valueChanged, this, &PreviewsSettingsPage::changed);
 }
 
 PreviewsSettingsPage::~PreviewsSettingsPage()
@@ -168,17 +168,17 @@ void PreviewsSettingsPage::loadPreviewPlugins()
 {
     QAbstractItemModel* model = m_listView->model();
 
-    const KService::List plugins = KServiceTypeTrader::self()->query(QStringLiteral("ThumbCreator"));
-    for (const KService::Ptr& service : plugins) {
-        const bool configurable = service->property(QStringLiteral("Configurable"), QVariant::Bool).toBool();
-        const bool show = m_enabledPreviewPlugins.contains(service->desktopEntryName());
+    const QVector<KPluginMetaData> plugins = KIO::PreviewJob::availableThumbnailerPlugins();
+    for (const KPluginMetaData &plugin : plugins) {
+        const bool configurable = plugin.value(QStringLiteral("Configurable"), false);
+        const bool show = m_enabledPreviewPlugins.contains(plugin.pluginId());
 
         model->insertRow(0);
         const QModelIndex index = model->index(0, 0);
         model->setData(index, show, Qt::CheckStateRole);
         model->setData(index, configurable, ServiceModel::ConfigurableRole);
-        model->setData(index, service->name(), Qt::DisplayRole);
-        model->setData(index, service->desktopEntryName(), ServiceModel::DesktopEntryNameRole);
+        model->setData(index, plugin.name(), Qt::DisplayRole);
+        model->setData(index, plugin.pluginId(), ServiceModel::DesktopEntryNameRole);
     }
 
     model->sort(Qt::DisplayRole);
index df627fa1c7f2a1fcf0ddbf8dea1a62173cec5855..048ee0e9affec13bcaeefbd9249b054ea8b857b6 100644 (file)
@@ -15,12 +15,12 @@ TrashSettingsPage::TrashSettingsPage(QWidget* parent) :
 {
     QFormLayout* topLayout = new QFormLayout(this);
 
-    m_proxy = new KCModuleProxy(QStringLiteral("kcmtrash"));
+    m_proxy = new KCModuleProxy(KPluginMetaData(QStringLiteral("kcm_trash")));
     topLayout->addRow(m_proxy);
 
     loadSettings();
 
-    connect(m_proxy, QOverload<bool>::of(&KCModuleProxy::changed), this, &TrashSettingsPage::changed);
+    connect(m_proxy, &KCModuleProxy::changed, this, &TrashSettingsPage::changed);
 }
 
 TrashSettingsPage::~TrashSettingsPage()
index cb66870afe83e722fc381bc7da51edfeabc2a2eb..a4663e94a301b4926b95f975ba932c209db9af6d 100644 (file)
@@ -27,7 +27,7 @@ DolphinFontRequester::DolphinFontRequester(QWidget* parent) :
     m_modeCombo = new QComboBox(this);
     m_modeCombo->addItem(i18nc("@item:inlistbox Font", "System Font"));
     m_modeCombo->addItem(i18nc("@item:inlistbox Font", "Custom Font"));
-    connect(m_modeCombo, QOverload<int>::of(&QComboBox::activated),
+    connect(m_modeCombo, &QComboBox::activated,
             this, &DolphinFontRequester::changeMode);
 
     m_chooseFontButton = new QPushButton(i18nc("@action:button Choose font", "Choose..."), this);
index 7ea8d5809410ecbf7b6ab8f2330f92532e127b94..1e109aab05337e81a44945f00a4f5e96c43b66be 100644 (file)
@@ -112,7 +112,7 @@ ViewSettingsTab::ViewSettingsTab(Mode mode, QWidget* parent) :
         sortingModeGroup->addButton(m_sizeOfContents);
 
         m_recursiveDirectorySizeLimit = new QSpinBox();
-        connect(m_recursiveDirectorySizeLimit, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int value) {
+        connect(m_recursiveDirectorySizeLimit, &QSpinBox::valueChanged, this, [this](int value) {
             m_recursiveDirectorySizeLimit->setSuffix(i18np(" level deep", " levels deep", value));
         });
         m_recursiveDirectorySizeLimit->setRange(1, 20);
@@ -155,16 +155,16 @@ ViewSettingsTab::ViewSettingsTab(Mode mode, QWidget* parent) :
 
     switch (m_mode) {
     case IconsMode:
-        connect(m_widthBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ViewSettingsTab::changed);
-        connect(m_maxLinesBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ViewSettingsTab::changed);
+        connect(m_widthBox, &QComboBox::currentIndexChanged, this, &ViewSettingsTab::changed);
+        connect(m_maxLinesBox, &QComboBox::currentIndexChanged, this, &ViewSettingsTab::changed);
         break;
     case CompactMode:
-        connect(m_widthBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ViewSettingsTab::changed);
+        connect(m_widthBox, &QComboBox::currentIndexChanged, this, &ViewSettingsTab::changed);
         break;
     case DetailsMode:
         connect(m_expandableFolders, &QCheckBox::toggled, this, &ViewSettingsTab::changed);
 #ifndef Q_OS_WIN
-        connect(m_recursiveDirectorySizeLimit, QOverload<int>::of(&QSpinBox::valueChanged), this, &ViewSettingsTab::changed);
+        connect(m_recursiveDirectorySizeLimit, &QSpinBox::valueChanged, this, &ViewSettingsTab::changed);
         connect(m_numberOfItems, &QRadioButton::toggled, this, &ViewSettingsTab::changed);
         connect(m_sizeOfContents, &QRadioButton::toggled, this, [=]() {
             m_recursiveDirectorySizeLimit->setEnabled(m_sizeOfContents->isChecked());
index 6659d79b63f0b3c9f23cefd0cb4829ed7661e4df..318c2e1cf176fbc71cf81cc2878d621212702b38 100644 (file)
@@ -150,11 +150,11 @@ ViewPropertiesDialog::ViewPropertiesDialog(DolphinView* dolphinView) :
     layout->addRow(QString(), m_showHiddenFiles);
     layout->addRow(QString(), m_sortHiddenLast);
 
-    connect(m_viewMode, QOverload<int>::of(&QComboBox::currentIndexChanged),
+    connect(m_viewMode, &QComboBox::currentIndexChanged,
             this, &ViewPropertiesDialog::slotViewModeChanged);
-    connect(m_sorting, QOverload<int>::of(&QComboBox::currentIndexChanged),
+    connect(m_sorting, &QComboBox::currentIndexChanged,
             this, &ViewPropertiesDialog::slotSortingChanged);
-    connect(m_sortOrder, QOverload<int>::of(&QComboBox::currentIndexChanged),
+    connect(m_sortOrder, &QComboBox::currentIndexChanged,
             this, &ViewPropertiesDialog::slotSortOrderChanged);
     connect(m_sortFoldersFirst, &QCheckBox::clicked,
             this, &ViewPropertiesDialog::slotSortFoldersFirstChanged);
index e9a0e2dced946f274af4770d5ac4e792991a2935..64003850c568ad61f4b7a78bf45a68e68d279571 100644 (file)
@@ -1,78 +1,68 @@
 set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
 
-find_package(Qt5Test CONFIG REQUIRED)
+find_package(Qt${QT_MAJOR_VERSION}Test CONFIG REQUIRED)
 include(ECMAddTests)
 
 include(FindGem)  # For servicemenutest, see bottom of this file
 
 # KItemSetTest
-ecm_add_test(kitemsettest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test)
+ecm_add_test(kitemsettest.cpp LINK_LIBRARIES dolphinprivate Qt${QT_MAJOR_VERSION}::Test)
 
 # KItemRangeTest
-ecm_add_test(kitemrangetest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test)
+ecm_add_test(kitemrangetest.cpp LINK_LIBRARIES dolphinprivate Qt${QT_MAJOR_VERSION}::Test)
 
 
 # KItemListSelectionManagerTest
-ecm_add_test(kitemlistselectionmanagertest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test)
+ecm_add_test(kitemlistselectionmanagertest.cpp LINK_LIBRARIES dolphinprivate Qt${QT_MAJOR_VERSION}::Test)
 
 # KItemListControllerTest
 ecm_add_test(kitemlistcontrollertest.cpp testdir.cpp
 TEST_NAME kitemlistcontrollertest
-LINK_LIBRARIES dolphinprivate Qt5::Test)
+LINK_LIBRARIES dolphinprivate Qt${QT_MAJOR_VERSION}::Test)
 
 # KFileItemListViewTest
 ecm_add_test(kfileitemlistviewtest.cpp testdir.cpp
 TEST_NAME kfileitemlistviewtest
-LINK_LIBRARIES dolphinprivate Qt5::Test)
+LINK_LIBRARIES dolphinprivate Qt${QT_MAJOR_VERSION}::Test)
 
 # KFileItemModelTest
 ecm_add_test(kfileitemmodeltest.cpp testdir.cpp
 TEST_NAME kfileitemmodeltest
-LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test)
+LINK_LIBRARIES dolphinprivate dolphinstatic Qt${QT_MAJOR_VERSION}::Test)
 
 # KFileItemModelBenchmark, not run automatically with `ctest` or `make test`
 add_executable(kfileitemmodelbenchmark kfileitemmodelbenchmark.cpp testdir.cpp)
-target_link_libraries(kfileitemmodelbenchmark dolphinprivate Qt5::Test)
+target_link_libraries(kfileitemmodelbenchmark dolphinprivate Qt${QT_MAJOR_VERSION}::Test)
 
 # KItemListKeyboardSearchManagerTest
-ecm_add_test(kitemlistkeyboardsearchmanagertest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test)
+ecm_add_test(kitemlistkeyboardsearchmanagertest.cpp LINK_LIBRARIES dolphinprivate Qt${QT_MAJOR_VERSION}::Test)
 
 # DolphinSearchBox
 if (KF5Baloo_FOUND)
     ecm_add_test(dolphinsearchboxtest.cpp
     TEST_NAME dolphinsearchboxtest
-    LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test)
+    LINK_LIBRARIES dolphinprivate dolphinstatic Qt${QT_MAJOR_VERSION}::Test)
 endif()
 
 # DolphinQuery
 if (KF5Baloo_FOUND)
     ecm_add_test(dolphinquerytest.cpp
     TEST_NAME dolphinquerytest
-    LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test)
+    LINK_LIBRARIES dolphinprivate dolphinstatic Qt${QT_MAJOR_VERSION}::Test)
 endif()
 
-# KStandardItemModelTest
-ecm_add_test(kstandarditemmodeltest.cpp
-TEST_NAME kstandarditemmodeltest
-LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test)
-
 # ViewPropertiesTest
 ecm_add_test(viewpropertiestest.cpp testdir.cpp
 TEST_NAME viewpropertiestest
-LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test)
+LINK_LIBRARIES dolphinprivate dolphinstatic Qt${QT_MAJOR_VERSION}::Test)
 
 # DolphinMainWindowTest
 ecm_add_test(dolphinmainwindowtest.cpp ${CMAKE_SOURCE_DIR}/src/dolphin.qrc
 TEST_NAME dolphinmainwindowtest
-LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test)
+LINK_LIBRARIES dolphinprivate dolphinstatic Qt${QT_MAJOR_VERSION}::Test)
 
 # DragAndDropHelperTest
-ecm_add_test(draganddrophelpertest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test)
-
-# PlacesItemModelTest
-ecm_add_test(placesitemmodeltest.cpp
-TEST_NAME placesitemmodeltest
-LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test)
+ecm_add_test(draganddrophelpertest.cpp LINK_LIBRARIES dolphinprivate Qt${QT_MAJOR_VERSION}::Test)
 
 find_gem(test-unit)
 set_package_properties(Gem:test-unit PROPERTIES
index 7a22a1a7f7eb75dd50b5923a0ab513706f9cb9c6..679b8ab3a3b5739b697c01856d40374889d7e113 100644 (file)
@@ -73,6 +73,10 @@ private Q_SLOTS:
     void testNameFilter();
     void testEmptyPath();
     void testRefreshExpandedItem();
+    void testAddItemToFilteredExpandedFolder();
+    void testDeleteItemsWithExpandedFolderWithFilter();
+    void testRefreshItemsWithFilter();
+    void testRefreshExpandedFolderWithFilter();
     void testRemoveHiddenItems();
     void collapseParentOfHiddenItems();
     void removeParentOfHiddenItems();
@@ -516,7 +520,7 @@ void KFileItemModelTest::testExpandItems()
     // Test expanding subfolders in a folder with the items "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1".
     // Besides testing the basic item expansion functionality, the test makes sure that
     // KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& b)
-    // yields the correct result for "a/a/1" and "a/a-1/", whis is non-trivial because they share the
+    // yields the correct result for "a/a/1" and "a/a-1/", which is non-trivial because they share the
     // first three characters.
     QSet<QByteArray> originalModelRoles = m_model->roles();
     QSet<QByteArray> modelRoles = originalModelRoles;
@@ -1143,6 +1147,219 @@ void KFileItemModelTest::testRefreshExpandedItem()
     QVERIFY(m_model->isExpanded(0));
 }
 
+/**
+ * Verifies that adding an item to an expanded folder that's filtered makes the parental chain visible.
+ */
+void KFileItemModelTest::testAddItemToFilteredExpandedFolder()
+{
+    QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
+    QSignalSpy fileItemsChangedSpy(m_model, &KFileItemModel::fileItemsChanged);
+
+    QSet<QByteArray> modelRoles = m_model->roles();
+    modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
+    m_model->setRoles(modelRoles);
+
+    m_testDir->createFile("a/b/file");
+
+    m_model->loadDirectory(m_testDir->url());
+    QVERIFY(itemsInsertedSpy.wait());
+    QCOMPARE(m_model->count(), 1); // "a
+
+    // Expand "a/".
+    m_model->setExpanded(0, true);
+    QVERIFY(itemsInsertedSpy.wait());   
+
+    // Expand "a/b/".
+    m_model->setExpanded(1, true);
+    QVERIFY(itemsInsertedSpy.wait());
+
+    QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/file"
+
+    const QUrl urlB = m_model->fileItem(1).url();
+
+    // Set a filter that matches ".txt" extension
+    m_model->setNameFilter("*.txt");
+    QCOMPARE(m_model->count(), 0); // Everything got hidden since we don't have a .txt file yet
+
+    m_model->slotItemsAdded(urlB, KFileItemList() << KFileItem(QUrl("a/b/newItem.txt")));
+    m_model->slotCompleted();
+
+    // Entire parental chain should now be shown
+    QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/newItem.txt"
+    QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "newItem.txt");
+
+    // Items should be indented in hierarchy
+    QCOMPARE(m_model->expandedParentsCount(0), 0);
+    QCOMPARE(m_model->expandedParentsCount(1), 1);
+    QCOMPARE(m_model->expandedParentsCount(2), 2);
+}
+
+/**
+ * Verifies that deleting the last filter-passing child from expanded folders
+ * makes the parental chain hidden.
+ */
+void KFileItemModelTest::testDeleteItemsWithExpandedFolderWithFilter()
+{
+    QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
+    QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
+    
+    QSet<QByteArray> modelRoles = m_model->roles();
+    modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
+    m_model->setRoles(modelRoles);
+
+    m_testDir->createFile("a/b/file");
+
+    m_model->loadDirectory(m_testDir->url());
+    QVERIFY(itemsInsertedSpy.wait());
+    QCOMPARE(m_model->count(), 1); // "a
+
+    // Expand "a/".
+    m_model->setExpanded(0, true);
+    QVERIFY(itemsInsertedSpy.wait());   
+
+    // Expand "a/b/".
+    m_model->setExpanded(1, true);
+    QVERIFY(itemsInsertedSpy.wait());
+
+    QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/file"
+
+    // Set a filter that matches "file" extension
+    m_model->setNameFilter("file");
+    QCOMPARE(m_model->count(), 3); // Everything is still shown
+
+    // Delete "file"
+    QCOMPARE(itemsRemovedSpy.count(), 0);
+    m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(2));
+    QCOMPARE(itemsRemovedSpy.count(), 1);
+
+    // Entire parental chain should now be filtered
+    QCOMPARE(m_model->count(), 0);
+    QCOMPARE(m_model->m_filteredItems.size(), 2);
+}
+
+/**
+ * Verifies that the fileItemsChanged signal is raised with the correct index after renaming files with filter set.
+ * The rename operation will cause one item to be filtered out and another item to be reordered.
+ */
+void KFileItemModelTest::testRefreshItemsWithFilter()
+{
+    QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
+    QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
+    QSignalSpy itemsChangedSpy(m_model, &KFileItemModel::itemsChanged);
+    QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved);
+
+    // Creates three .txt files
+    m_testDir->createFiles({"b.txt", "c.txt", "d.txt"});
+
+    m_model->loadDirectory(m_testDir->url());
+    QVERIFY(itemsInsertedSpy.wait());
+
+    QCOMPARE(m_model->count(), 3); // "b.txt", "c.txt", "d.txt"
+
+    // Set a filter that matches ".txt" extension
+    m_model->setNameFilter("*.txt");
+    QCOMPARE(m_model->count(), 3); // Still all items are shown
+    QCOMPARE(itemsInModel(), QStringList() << "b.txt" << "c.txt" << "d.txt");
+
+    // Objects used to rename
+    const KFileItem fileItemC_txt = m_model->fileItem(1);
+    KFileItem fileItemC_cfg = fileItemC_txt;
+    fileItemC_cfg.setUrl(QUrl("c.cfg"));
+
+    const KFileItem fileItemD_txt = m_model->fileItem(2);
+    KFileItem fileItemA_txt = fileItemD_txt;
+    fileItemA_txt.setUrl(QUrl("a.txt"));
+
+    // Rename "c.txt" to "c.cfg"; and rename "d.txt" to "a.txt"
+    QCOMPARE(itemsRemovedSpy.count(), 0);
+    QCOMPARE(itemsChangedSpy.count(), 0);
+    m_model->slotRefreshItems({qMakePair(fileItemC_txt, fileItemC_cfg), qMakePair(fileItemD_txt, fileItemA_txt)});
+    QCOMPARE(itemsRemovedSpy.count(), 1);
+    QCOMPARE(itemsChangedSpy.count(), 1);
+    QCOMPARE(m_model->count(), 2); // Only "a.txt" and "b.txt". "c.cfg" got filtered out
+
+    QList<QVariant> arguments = itemsChangedSpy.takeLast();
+    KItemRangeList itemRangeList = arguments.at(0).value<KItemRangeList>();
+
+    // We started with the order "b.txt", "c.txt", "d.txt"
+    // "d.txt" started with index "2"
+    // "c.txt" got renamed and got filtered out
+    // "d.txt" index shifted from index "2" to "1"
+    // So we expect index "1" in this argument, meaning "d.txt" was renamed
+    QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 1));
+
+    // Re-sorting is done asynchronously:
+    QCOMPARE(itemsInModel(), QStringList() << "b.txt" << "a.txt"); // Files should still be in the incorrect order
+    QVERIFY(itemsMovedSpy.wait());
+    QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt"); // Files were re-sorted and should now be in the correct order
+}
+
+
+/**
+ * Verifies that parental chains are hidden and shown as needed while their children get filtered/unfiltered due to renaming.
+ * Also verifies that the "isExpanded" and "expandedParentsCount" values are kept for expanded folders that get refreshed.
+ */
+void KFileItemModelTest::testRefreshExpandedFolderWithFilter() {
+    QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
+    QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
+
+    QSet<QByteArray> modelRoles = m_model->roles();
+    modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
+    m_model->setRoles(modelRoles);
+
+    m_testDir->createFile("a/b/someFolder/someFile");
+
+    m_model->loadDirectory(m_testDir->url());
+    QVERIFY(itemsInsertedSpy.wait());
+
+    QCOMPARE(m_model->count(), 1); // Only "a/"
+
+    // Expand "a/".
+    m_model->setExpanded(0, true);
+    QVERIFY(itemsInsertedSpy.wait());   
+
+    // Expand "a/b/".
+    m_model->setExpanded(1, true);
+    QVERIFY(itemsInsertedSpy.wait());
+
+    // Expand "a/b/someFolder/".
+    m_model->setExpanded(2, true);
+    QVERIFY(itemsInsertedSpy.wait());
+    QCOMPARE(m_model->count(), 4); // 4 items: "a/", "a/b/", "a/b/someFolder", "a/b/someFolder/someFile"
+
+    // Set a filter that matches the expanded folder "someFolder"
+    m_model->setNameFilter("someFolder");
+    QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/someFolder"
+
+    // Objects used to rename
+    const KFileItem fileItemA = m_model->fileItem(0);
+    KFileItem fileItemARenamed = fileItemA;
+    fileItemARenamed.setUrl(QUrl("a_renamed"));    
+
+    const KFileItem fileItemSomeFolder = m_model->fileItem(2);
+    KFileItem fileItemRenamedFolder = fileItemSomeFolder;
+    fileItemRenamedFolder.setUrl(QUrl("/a_renamed/b/renamedFolder"));
+
+    // Rename "a" to "a_renamed"
+    // This way we test if the algorithm is sane as to NOT hide "a_renamed" since it will have visible children
+    m_model->slotRefreshItems({qMakePair(fileItemA, fileItemARenamed)});
+    QCOMPARE(m_model->count(), 3); // Entire parental chain must still be shown
+    QCOMPARE(itemsInModel(), QStringList() << "a_renamed" << "b" << "someFolder");
+
+    // Rename "a_renamed" back to "a"; and "someFolder" to "renamedFolder"
+    m_model->slotRefreshItems({qMakePair(fileItemARenamed, fileItemA), qMakePair(fileItemSomeFolder, fileItemRenamedFolder)});
+    QCOMPARE(m_model->count(), 0); // Entire parental chain became hidden
+
+    // Rename "renamedFolder" back to "someFolder". Filter is passing again
+    m_model->slotRefreshItems({qMakePair(fileItemRenamedFolder, fileItemSomeFolder)});
+    QCOMPARE(m_model->count(), 3); // Entire parental chain is shown again
+    QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "someFolder");
+
+    // slotRefreshItems() should preserve "isExpanded" and "expandedParentsCount" values explicitly in this case
+    QCOMPARE(m_model->m_itemData.at(2)->values.value("isExpanded").toBool(), true);
+    QCOMPARE(m_model->m_itemData.at(2)->values.value("expandedParentsCount"), 2);
+}
+
 /**
  * Verify that removing hidden files and folders from the model does not
  * result in a crash, see https://bugs.kde.org/show_bug.cgi?id=314046
@@ -1298,8 +1515,7 @@ void KFileItemModelTest::removeParentOfHiddenItems()
     // Simulate the deletion of the directory "a/b/".
     m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(1));
     QCOMPARE(itemsRemovedSpy.count(), 2);
-    QCOMPARE(m_model->count(), 1);
-    QCOMPARE(itemsInModel(), QStringList() << "a");
+    QCOMPARE(m_model->count(), 0); // "a" will be filtered out since it doesn't pass the filter and doesn't have visible children
 
     // Remove the filter -> only the file "a/1" should appear.
     m_model->setNameFilter(QString());
diff --git a/src/tests/kstandarditemmodeltest.cpp b/src/tests/kstandarditemmodeltest.cpp
deleted file mode 100644 (file)
index 943a852..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
- * SPDX-FileCopyrightText: 2011 Frank Reininghaus <frank78ac@googlemail.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "dolphindebug.h"
-
-#include "kitemviews/kstandarditem.h"
-#include "kitemviews/kstandarditemmodel.h"
-
-#include <QStandardPaths>
-#include <QTest>
-
-class KStandardItemModelTest : public QObject
-{
-    Q_OBJECT
-
-private Q_SLOTS:
-    void initTestCase();
-    void init();
-    void cleanup();
-
-    void testNewItems();
-    void testRemoveItems();
-
-private:
-    bool isModelConsistent() const;
-
-private:
-    KStandardItemModel* m_model;
-};
-
-void KStandardItemModelTest::initTestCase()
-{
-    QStandardPaths::setTestModeEnabled(true);
-}
-
-void KStandardItemModelTest::init()
-{
-    m_model = new KStandardItemModel();
-}
-
-void KStandardItemModelTest::cleanup()
-{
-    delete m_model;
-    m_model = nullptr;
-}
-
-void KStandardItemModelTest::testNewItems()
-{
-    m_model->insertItem(0, new KStandardItem("item 1"));
-    m_model->insertItem(0, new KStandardItem("item 2"));
-    m_model->insertItem(2, new KStandardItem("item 3"));
-    m_model->appendItem(new KStandardItem("item 4"));
-    m_model->insertItem(-1, new KStandardItem("invalid 1"));
-    m_model->insertItem(5, new KStandardItem("invalid 2"));
-    QCOMPARE(m_model->count(), 4);
-    QCOMPARE(m_model->item(0)->text(), QString("item 2"));
-    QCOMPARE(m_model->item(1)->text(), QString("item 1"));
-    QCOMPARE(m_model->item(2)->text(), QString("item 3"));
-    QCOMPARE(m_model->item(3)->text(), QString("item 4"));
-
-    QVERIFY(isModelConsistent());
-}
-
-void KStandardItemModelTest::testRemoveItems()
-{
-    for (int i = 1; i <= 10; ++i) {
-        m_model->appendItem(new KStandardItem("item " + QString::number(i)));
-    }
-
-    m_model->removeItem(0);
-    m_model->removeItem(3);
-    m_model->removeItem(5);
-    m_model->removeItem(-1);
-    QCOMPARE(m_model->count(), 7);
-    QCOMPARE(m_model->item(0)->text(), QString("item 2"));
-    QCOMPARE(m_model->item(1)->text(), QString("item 3"));
-    QCOMPARE(m_model->item(2)->text(), QString("item 4"));
-    QCOMPARE(m_model->item(3)->text(), QString("item 6"));
-    QCOMPARE(m_model->item(4)->text(), QString("item 7"));
-    QCOMPARE(m_model->item(5)->text(), QString("item 9"));
-    QCOMPARE(m_model->item(6)->text(), QString("item 10"));
-}
-
-bool KStandardItemModelTest::isModelConsistent() const
-{
-    if (m_model->m_items.count() != m_model->m_indexesForItems.count()) {
-        return false;
-    }
-
-    for (int i = 0; i < m_model->count(); ++i) {
-        const KStandardItem* item = m_model->item(i);
-        if (!item) {
-            qCWarning(DolphinDebug) << "Item" << i << "is null";
-            return false;
-        }
-
-        const int itemIndex = m_model->index(item);
-        if (itemIndex != i) {
-            qCWarning(DolphinDebug) << "Item" << i << "has a wrong index:" << itemIndex;
-            return false;
-        }
-    }
-
-    return true;
-}
-
-QTEST_GUILESS_MAIN(KStandardItemModelTest)
-
-#include "kstandarditemmodeltest.moc"
diff --git a/src/tests/placesitemmodeltest.cpp b/src/tests/placesitemmodeltest.cpp
deleted file mode 100644 (file)
index 15a4946..0000000
+++ /dev/null
@@ -1,939 +0,0 @@
-/*
- * SPDX-FileCopyrightText: 2017 Renato Araujo Oliveira <renato.araujo@kdab.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include <QTest>
-#include <QSignalSpy>
-#include <QStandardPaths>
-#include <QAction>
-#include <QDBusInterface>
-
-#include <KBookmarkManager>
-#include <KConfig>
-#include <KConfigGroup>
-#include <KAboutData>
-#include <KFilePlacesModel>
-#include <KProtocolInfo>
-
-#include "dolphin_generalsettings.h"
-#include "panels/places/placesitemmodel.h"
-#include "panels/places/placesitem.h"
-#include "views/viewproperties.h"
-
-Q_DECLARE_METATYPE(KItemRangeList)
-Q_DECLARE_METATYPE(KItemRange)
-
-static QString bookmarksFile()
-{
-    return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel";
-}
-
-class PlacesItemModelTest : public QObject
-{
-    Q_OBJECT
-
-private Q_SLOTS:
-    void init();
-    void cleanup();
-
-    void initTestCase();
-    void cleanupTestCase();
-
-    void testModelSort();
-    void testGroups();
-    void testDeletePlace();
-    void testPlaceItem_data();
-    void testPlaceItem();
-    void testTearDownDevice();
-    void testDefaultViewProperties_data();
-    void testDefaultViewProperties();
-    void testClear();
-    void testHideItem();
-    void testSystemItems();
-    void testEditBookmark();
-    void testEditAfterCreation();
-    void testEditMetadata();
-    void testRefresh();
-    void testIcons_data();
-    void testIcons();
-    void testDragAndDrop();
-    void testHideDevices();
-    void testDuplicatedEntries();
-    void renameAfterCreation();
-
-private:
-    PlacesItemModel* m_model;
-    QSet<int> m_tobeRemoved;
-    QMap<QString, QDBusInterface *> m_interfacesMap;
-    int m_expectedModelCount = 14;
-    bool m_hasDesktopFolder = false;
-    bool m_hasDocumentsFolder = false;
-    bool m_hasDownloadsFolder = false;
-    bool m_hasMusicFolder = false;
-    bool m_hasPicturesFolder = false;
-    bool m_hasVideosFolder = false;
-
-    void setBalooEnabled(bool enabled);
-    int indexOf(const QUrl &url);
-    QDBusInterface *fakeManager();
-    QDBusInterface *fakeDevice(const QString &udi);
-    QStringList placesUrls(PlacesItemModel *model = nullptr) const;
-    QStringList initialUrls() const;
-    void createPlaceItem(const QString &text, const QUrl &url, const QString &icon);
-    void schedulePlaceRemoval(int index);
-    void cancelPlaceRemoval(int index);
-    QMimeData *createMimeData(const QList<int> &indexes) const;
-    void increaseIndexIfNeeded(int &index) const;
-    QTemporaryDir m_tempHomeDir;
-};
-
-#define CHECK_PLACES_URLS(urls)                                             \
-    {                                                                       \
-        QStringList places = placesUrls();                                  \
-        if (places != urls) {                                               \
-            qWarning() << "Expected:" << urls;                              \
-            qWarning() << "Got:" << places;                                 \
-            QCOMPARE(places, urls);                                         \
-        }                                                                   \
-    }
-
-void PlacesItemModelTest::setBalooEnabled(bool enabled)
-{
-    KConfig config(QStringLiteral("baloofilerc"));
-    KConfigGroup basicSettings = config.group("Basic Settings");
-    basicSettings.writeEntry("Indexing-Enabled", enabled);
-    config.sync();
-}
-
-int PlacesItemModelTest::indexOf(const QUrl &url)
-{
-    for (int r = 0; r < m_model->count(); r++) {
-        if (m_model->placesItem(r)->url() == url) {
-            return r;
-        }
-    }
-    return -1;
-}
-
-QDBusInterface *PlacesItemModelTest::fakeManager()
-{
-    return fakeDevice(QStringLiteral("/org/kde/solid/fakehw"));
-}
-
-QDBusInterface *PlacesItemModelTest::fakeDevice(const QString &udi)
-{
-    if (m_interfacesMap.contains(udi)) {
-        return m_interfacesMap[udi];
-    }
-
-    QDBusInterface *iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), udi);
-    m_interfacesMap[udi] = iface;
-
-    return iface;
-}
-
-QStringList PlacesItemModelTest::placesUrls(PlacesItemModel *model) const
-{
-    QStringList urls;
-    if (!model) {
-        model = m_model;
-    }
-
-    for (int row = 0; row < model->count(); ++row) {
-        urls << model->placesItem(row)->url().toDisplayString(QUrl::PreferLocalFile);
-    }
-    return urls;
-}
-
-QStringList PlacesItemModelTest::initialUrls() const
-{
-    static QStringList urls;
-    if (urls.isEmpty()) {
-        urls << QDir::homePath();
-
-        if (m_hasDesktopFolder) {
-            urls << QDir::homePath() + QStringLiteral("/Desktop");
-        }
-
-        if (m_hasDocumentsFolder) {
-            urls << QDir::homePath() + QStringLiteral("/Documents");
-        }
-
-        if (m_hasDownloadsFolder) {
-            urls << QDir::homePath() + QStringLiteral("/Downloads");
-        }
-
-        if (m_hasMusicFolder) {
-            urls << QDir::homePath() + QStringLiteral("/Music");
-        }
-
-        if (m_hasPicturesFolder) {
-            urls << QDir::homePath() + QStringLiteral("/Pictures");
-        }
-
-        if (m_hasVideosFolder) {
-            urls << QDir::homePath() + QStringLiteral("/Videos");
-        }
-
-        urls << QStringLiteral("trash:/")
-             << QStringLiteral("remote:/")
-             << QStringLiteral("/media/nfs");
-
-        if (qEnvironmentVariableIsSet("KDE_FULL_SESSION") && KProtocolInfo::isKnownProtocol(QStringLiteral("recentlyused"))) {
-            urls << QStringLiteral("recentlyused:/files");
-            urls << QStringLiteral("recentlyused:/locations");
-        } else {
-            urls << QStringLiteral("timeline:/today")
-                 << QStringLiteral("timeline:/yesterday");
-        }
-
-        urls << QStringLiteral("search:/documents") << QStringLiteral("search:/images") << QStringLiteral("search:/audio") << QStringLiteral("search:/videos")
-             << QStringLiteral("/foreign")
-             << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom");
-    }
-    return urls;
-}
-
-void PlacesItemModelTest::createPlaceItem(const QString &text, const QUrl &url, const QString &icon)
-{
-    m_model->createPlacesItem(text, url, icon);
-}
-
-void PlacesItemModelTest::schedulePlaceRemoval(int index)
-{
-    m_tobeRemoved.insert(index);
-}
-
-void PlacesItemModelTest::cancelPlaceRemoval(int index)
-{
-    m_tobeRemoved.remove(index);
-}
-
-QMimeData *PlacesItemModelTest::createMimeData(const QList<int> &indexes) const
-{
-    QByteArray itemData;
-    QDataStream stream(&itemData, QIODevice::WriteOnly);
-    QList<QUrl> urls;
-
-    for (int index : indexes) {
-        const QUrl itemUrl = m_model->placesItem(index)->url();
-        if (itemUrl.isValid()) {
-            urls << itemUrl;
-        }
-        stream << index;
-    }
-
-    QMimeData* mimeData = new QMimeData();
-    mimeData->setUrls(urls);
-    // copied from PlacesItemModel::internalMimeType()
-    const QString internalMimeType = "application/x-dolphinplacesmodel-" +
-            QString::number((qptrdiff)m_model);
-    mimeData->setData(internalMimeType, itemData);
-    return mimeData;
-}
-
-void PlacesItemModelTest::increaseIndexIfNeeded(int &index) const
-{
-    if (m_hasDesktopFolder) {
-        index++;
-    }
-    if (m_hasDocumentsFolder) {
-        index++;
-    }
-    if (m_hasDownloadsFolder) {
-        index++;
-    }
-    if (m_hasMusicFolder) {
-        index++;
-    }
-    if (m_hasPicturesFolder) {
-        index++;
-    }
-    if (m_hasVideosFolder) {
-        index++;
-    }
-}
-
-void PlacesItemModelTest::init()
-{
-    m_model = new PlacesItemModel();
-    // WORKAROUND: need to wait for bookmark to load
-    QTest::qWait(300);
-    QCOMPARE(m_model->count(), m_expectedModelCount);
-}
-
-void PlacesItemModelTest::cleanup()
-{
-    const auto tobeRemoved = m_tobeRemoved;
-    for (const int i : tobeRemoved) {
-        int before = m_model->count();
-        m_model->deleteItem(i);
-        QTRY_COMPARE(m_model->count(), before - 1);
-    }
-    m_tobeRemoved.clear();
-    delete m_model;
-    m_model = nullptr;
-}
-
-void PlacesItemModelTest::initTestCase()
-{
-    QVERIFY(m_tempHomeDir.isValid());
-    QVERIFY(qputenv("HOME", m_tempHomeDir.path().toUtf8()));
-    QVERIFY(qputenv("KDE_FORK_SLAVES", "yes"));
-
-    QStandardPaths::setTestModeEnabled(true);
-
-    const QString fakeHw = QFINDTESTDATA("data/fakecomputer.xml");
-    QVERIFY(!fakeHw.isEmpty());
-    qputenv("SOLID_FAKEHW", QFile::encodeName(fakeHw));
-
-    setBalooEnabled(true);
-    const QString bookmarsFileName = bookmarksFile();
-    if (QFileInfo::exists(bookmarsFileName)) {
-        // Ensure we'll have a clean bookmark file to start
-        QVERIFY(QFile::remove(bookmarsFileName));
-    }
-
-    if (QDir(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)).exists()) {
-        m_hasDesktopFolder = true;
-        m_expectedModelCount++;
-    }
-
-    if (QDir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).exists()) {
-        m_hasDocumentsFolder = true;
-        m_expectedModelCount++;
-    }
-
-    if (QDir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).exists()) {
-        m_hasDownloadsFolder = true;
-        m_expectedModelCount++;
-    }
-
-    if (QDir(QStandardPaths::writableLocation(QStandardPaths::MusicLocation)).exists()) {
-        m_hasMusicFolder = true;
-        m_expectedModelCount++;
-    }
-
-    if (QDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)).exists()) {
-        m_hasPicturesFolder = true;
-        m_expectedModelCount++;
-    }
-
-    if (QDir(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation)).exists()) {
-        m_hasVideosFolder = true;
-        m_expectedModelCount++;
-    }
-
-    qRegisterMetaType<KItemRangeList>();
-    qRegisterMetaType<KItemRange>();
-}
-
-void PlacesItemModelTest::cleanupTestCase()
-{
-    qDeleteAll(m_interfacesMap);
-    QFile::remove(bookmarksFile());
-}
-
-void PlacesItemModelTest::testModelSort()
-{
-    CHECK_PLACES_URLS(initialUrls());
-}
-
-void PlacesItemModelTest::testGroups()
-{
-    const auto groups = m_model->groups();
-    int expectedRemoteIndex = 2;
-    increaseIndexIfNeeded(expectedRemoteIndex);
-
-    QCOMPARE(groups.size(), 6);
-
-    QCOMPARE(groups.at(0).first, 0);
-    QCOMPARE(groups.at(0).second.toString(), QStringLiteral("Places"));
-
-    QCOMPARE(groups.at(1).first, expectedRemoteIndex);
-    QCOMPARE(groups.at(1).second.toString(), QStringLiteral("Remote"));
-
-    QCOMPARE(groups.at(2).first, expectedRemoteIndex + 2);
-    QCOMPARE(groups.at(2).second.toString(), QStringLiteral("Recent"));
-
-    QCOMPARE(groups.at(3).first, expectedRemoteIndex + 4);
-    QCOMPARE(groups.at(3).second.toString(), QStringLiteral("Search For"));
-
-    QCOMPARE(groups.at(4).first, expectedRemoteIndex + 8);
-    QCOMPARE(groups.at(4).second.toString(), QStringLiteral("Devices"));
-
-    QCOMPARE(groups.at(5).first, expectedRemoteIndex + 9);
-    QCOMPARE(groups.at(5).second.toString(), QStringLiteral("Removable Devices"));
-}
-
-void PlacesItemModelTest::testPlaceItem_data()
-{
-    QTest::addColumn<QUrl>("url");
-    QTest::addColumn<bool>("expectedIsHidden");
-    QTest::addColumn<bool>("expectedIsSystemItem");
-    QTest::addColumn<QString>("expectedGroup");
-    QTest::addColumn<bool>("expectedStorageSetupNeeded");
-
-    // places
-    QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << false << true << QStringLiteral("Places") << false;
-
-    // baloo -search
-    QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << false << true << QStringLiteral("Search For") << false;
-
-    // devices
-    QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << false << false << QStringLiteral("Removable Devices") << false;
-}
-
-void PlacesItemModelTest::testPlaceItem()
-{
-    QFETCH(QUrl, url);
-    QFETCH(bool, expectedIsHidden);
-    QFETCH(bool, expectedIsSystemItem);
-    QFETCH(QString, expectedGroup);
-    QFETCH(bool, expectedStorageSetupNeeded);
-
-    const int index = indexOf(url);
-    PlacesItem *item = m_model->placesItem(index);
-    QCOMPARE(item->url(), url);
-    QCOMPARE(item->isHidden(), expectedIsHidden);
-    QCOMPARE(item->isSystemItem(), expectedIsSystemItem);
-    QCOMPARE(item->group(), expectedGroup);
-    QCOMPARE(item->storageSetupNeeded(), expectedStorageSetupNeeded);
-}
-
-void PlacesItemModelTest::testDeletePlace()
-{
-    const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
-    QStringList urls = initialUrls();
-    QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted);
-    QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved);
-
-    PlacesItemModel *model = new PlacesItemModel();
-
-    int tempDirIndex = 2;
-    increaseIndexIfNeeded(tempDirIndex);
-
-    // create a new place
-    createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString());
-    urls.insert(tempDirIndex, tempUrl.toLocalFile());
-
-    // check if the new entry was created
-    QTRY_COMPARE(itemsInsertedSpy.count(), 1);
-    CHECK_PLACES_URLS(urls);
-    QTRY_COMPARE(model->count(), m_model->count());
-
-    // delete item
-    m_model->deleteItem(tempDirIndex);
-
-    // make sure that the new item is removed
-    QTRY_COMPARE(itemsRemovedSpy.count(), 1);
-    QTRY_COMPARE(m_model->count(), m_expectedModelCount);
-    CHECK_PLACES_URLS(initialUrls());
-    QTRY_COMPARE(model->count(), m_model->count());
-}
-
-void PlacesItemModelTest::testTearDownDevice()
-{
-    const QUrl mediaUrl = QUrl::fromLocalFile(QStringLiteral("/media/XO-Y4"));
-    int index = indexOf(mediaUrl);
-    QVERIFY(index != -1);
-
-    auto ejectAction = m_model->ejectAction(index);
-    QVERIFY(!ejectAction);
-
-    auto teardownAction = m_model->teardownAction(index);
-    QVERIFY(teardownAction);
-
-    QCOMPARE(m_model->count(), m_expectedModelCount);
-
-    QSignalSpy spyItemsRemoved(m_model, &PlacesItemModel::itemsRemoved);
-    fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096");
-    QTRY_COMPARE(m_model->count(), m_expectedModelCount - 1);
-    QCOMPARE(spyItemsRemoved.count(), 1);
-    const QList<QVariant> spyItemsRemovedArgs = spyItemsRemoved.takeFirst();
-    const KItemRangeList removedRange = spyItemsRemovedArgs.at(0).value<KItemRangeList>();
-    QCOMPARE(removedRange.size(), 1);
-    QCOMPARE(removedRange.first().index, index);
-    QCOMPARE(removedRange.first().count, 1);
-
-    QCOMPARE(indexOf(mediaUrl), -1);
-
-    QSignalSpy spyItemsInserted(m_model, &PlacesItemModel::itemsInserted);
-    fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096");
-    QTRY_COMPARE(m_model->count(), m_expectedModelCount);
-    QCOMPARE(spyItemsInserted.count(), 1);
-    index = indexOf(mediaUrl);
-
-    const QList<QVariant> args = spyItemsInserted.takeFirst();
-    const KItemRangeList insertedRange = args.at(0).value<KItemRangeList>();
-    QCOMPARE(insertedRange.size(), 1);
-    QCOMPARE(insertedRange.first().index, index);
-    QCOMPARE(insertedRange.first().count, 1);
-}
-
-void PlacesItemModelTest::testDefaultViewProperties_data()
-{
-    QTest::addColumn<QUrl>("url");
-    QTest::addColumn<DolphinView::Mode>("expectedViewMode");
-    QTest::addColumn<bool>("expectedPreviewShow");
-    QTest::addColumn<QList<QByteArray> >("expectedVisibleRole");
-
-    // places
-    QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << DolphinView::IconsView << true << QList<QByteArray>({"text"});
-
-    // baloo -search
-    QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << DolphinView::DetailsView << false << QList<QByteArray>({"text", "path"});
-
-    // audio files
-    QTest::newRow("Places - Audio") << QUrl("search:/audio") << DolphinView::DetailsView << false << QList<QByteArray>({"text", "artist", "album"});
-
-    // devices
-    QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << DolphinView::IconsView << true << QList<QByteArray>({"text"});
-
-}
-
-void PlacesItemModelTest::testDefaultViewProperties()
-{
-    QFETCH(QUrl, url);
-    QFETCH(DolphinView::Mode, expectedViewMode);
-    QFETCH(bool, expectedPreviewShow);
-    QFETCH(QList<QByteArray>, expectedVisibleRole);
-
-    // In order to test the default view properties, turn off the global view properties and re-init the test to reload the model.
-    GeneralSettings* settings = GeneralSettings::self();
-    settings->setGlobalViewProps(false);
-    settings->save();
-    cleanup();
-    init();
-
-    ViewProperties properties(KFilePlacesModel::convertedUrl(url));
-    QCOMPARE(properties.viewMode(), expectedViewMode);
-    QCOMPARE(properties.previewsShown(), expectedPreviewShow);
-    QCOMPARE(properties.visibleRoles(), expectedVisibleRole);
-
-    settings->setGlobalViewProps(true);
-    settings->save();
-}
-
-void PlacesItemModelTest::testClear()
-{
-    QCOMPARE(m_model->count(), m_expectedModelCount);
-    m_model->clear();
-    QCOMPARE(m_model->count(), 0);
-    QCOMPARE(m_model->hiddenCount(), 0);
-    m_model->refresh();
-    QTRY_COMPARE(m_model->count(), m_expectedModelCount);
-}
-
-void PlacesItemModelTest::testHideItem()
-{
-    const QUrl mediaUrl = QUrl::fromLocalFile(QStringLiteral("/media/XO-Y4"));
-    const int index = indexOf(mediaUrl);
-
-    PlacesItem *item = m_model->placesItem(index);
-
-    QSignalSpy spyItemsRemoved(m_model, &PlacesItemModel::itemsRemoved);
-    QList<QVariant> spyItemsRemovedArgs;
-    KItemRangeList removedRange;
-
-    QSignalSpy spyItemsInserted(m_model, &PlacesItemModel::itemsInserted);
-    QList<QVariant> spyItemsInsertedArgs;
-    KItemRangeList insertedRange;
-    QVERIFY(item);
-
-    // hide an item
-    item->setHidden(true);
-
-    // check if items removed was fired
-    QTRY_COMPARE(m_model->count(), m_expectedModelCount - 1);
-    QCOMPARE(spyItemsRemoved.count(), 1);
-    spyItemsRemovedArgs = spyItemsRemoved.takeFirst();
-    removedRange = spyItemsRemovedArgs.at(0).value<KItemRangeList>();
-    QCOMPARE(removedRange.size(), 1);
-    QCOMPARE(removedRange.first().index, index);
-    QCOMPARE(removedRange.first().count, 1);
-
-    // allow model to show hidden items
-    m_model->setHiddenItemsShown(true);
-
-    // check if the items inserted was fired
-    spyItemsInsertedArgs = spyItemsInserted.takeFirst();
-    insertedRange = spyItemsInsertedArgs.at(0).value<KItemRangeList>();
-    QCOMPARE(insertedRange.size(), 1);
-    QCOMPARE(insertedRange.first().index, index);
-    QCOMPARE(insertedRange.first().count, 1);
-
-    // mark item as visible
-    item = m_model->placesItem(index);
-    item->setHidden(false);
-
-     // mark model to hide invisible items
-    m_model->setHiddenItemsShown(true);
-
-    QTRY_COMPARE(m_model->count(), m_expectedModelCount);
-}
-
-void PlacesItemModelTest::testSystemItems()
-{
-    int tempDirIndex = 2;
-    increaseIndexIfNeeded(tempDirIndex);
-
-    QCOMPARE(m_model->count(), m_expectedModelCount);
-    for (int r = 0; r < m_model->count(); r++) {
-        QCOMPARE(m_model->placesItem(r)->isSystemItem(), !m_model->placesItem(r)->device().isValid());
-    }
-
-    QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted);
-
-    // create a new entry (non system item)
-    createPlaceItem(QStringLiteral("Temporary Dir"),  QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)), QString());
-
-    // check if the new entry was created
-    QTRY_COMPARE(itemsInsertedSpy.count(), 1);
-
-    // make sure the new place get removed
-    schedulePlaceRemoval(tempDirIndex);
-
-    QList<QVariant> args = itemsInsertedSpy.takeFirst();
-    KItemRangeList range = args.at(0).value<KItemRangeList>();
-    QCOMPARE(range.first().index, tempDirIndex);
-    QCOMPARE(range.first().count, 1);
-    QVERIFY(!m_model->placesItem(tempDirIndex)->isSystemItem());
-    QCOMPARE(m_model->count(), m_expectedModelCount + 1);
-
-    QTest::qWait(300);
-    // check if the removal signal is correct
-    QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved);
-    m_model->deleteItem(tempDirIndex);
-    QTRY_COMPARE(itemsRemovedSpy.count(), 1);
-    args = itemsRemovedSpy.takeFirst();
-    range = args.at(0).value<KItemRangeList>();
-    QCOMPARE(range.first().index, tempDirIndex);
-    QCOMPARE(range.first().count, 1);
-    QTRY_COMPARE(m_model->count(), m_expectedModelCount);
-
-    //cancel removal (it was removed above)
-    cancelPlaceRemoval(tempDirIndex);
-}
-
-void PlacesItemModelTest::testEditBookmark()
-{
-    int tempDirIndex = 2;
-    increaseIndexIfNeeded(tempDirIndex);
-
-    QScopedPointer<PlacesItemModel> other(new PlacesItemModel());
-
-    createPlaceItem(QStringLiteral("Temporary Dir"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)), QString());
-
-    // make sure that the new item will be removed later
-    schedulePlaceRemoval(tempDirIndex);
-
-    QSignalSpy itemsChangedSply(m_model, &PlacesItemModel::itemsChanged);
-
-    // modify place text
-    m_model->item(tempDirIndex)->setText(QStringLiteral("Renamed place"));
-    m_model->refresh();
-
-    // check if the correct signal was fired
-    QTRY_COMPARE(itemsChangedSply.count(), 1);
-    QList<QVariant> args = itemsChangedSply.takeFirst();
-    KItemRangeList range = args.at(0).value<KItemRangeList>();
-    QCOMPARE(range.first().index, tempDirIndex);
-    QCOMPARE(range.first().count, 1);
-    QSet<QByteArray> roles = args.at(1).value<QSet<QByteArray> >();
-    QCOMPARE(roles.size(), 1);
-    QCOMPARE(*roles.begin(), QByteArrayLiteral("text"));
-    QCOMPARE(m_model->item(tempDirIndex)->text(), QStringLiteral("Renamed place"));
-
-    // check if the item was updated in the other model
-    QTRY_COMPARE(other->item(tempDirIndex)->text(), QStringLiteral("Renamed place"));
-}
-
-void PlacesItemModelTest::testEditAfterCreation()
-{
-    int tempDirIndex = 2;
-    increaseIndexIfNeeded(tempDirIndex);
-
-    const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
-    QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted);
-
-    // create a new place
-    createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString());
-    QTRY_COMPARE(itemsInsertedSpy.count(), 1);
-
-    PlacesItemModel *model = new PlacesItemModel();
-    QTRY_COMPARE(model->count(), m_model->count());
-
-    // make sure that the new item will be removed later
-    schedulePlaceRemoval(tempDirIndex);
-
-    // modify place text
-    PlacesItem *item = m_model->placesItem(tempDirIndex);
-    item->setText(QStringLiteral("Renamed place"));
-    m_model->refresh();
-
-    // check if the second model got the changes
-    QTRY_COMPARE(model->count(), m_model->count());
-    QTRY_COMPARE(model->placesItem(tempDirIndex)->text(), m_model->placesItem(tempDirIndex)->text());
-    QTRY_COMPARE(model->placesItem(tempDirIndex)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")),
-                 m_model->placesItem(tempDirIndex)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")));
-    QTRY_COMPARE(model->placesItem(tempDirIndex)->icon(), m_model->placesItem(tempDirIndex)->icon());
-    QTRY_COMPARE(model->placesItem(tempDirIndex)->url(), m_model->placesItem(tempDirIndex)->url());
-}
-
-void PlacesItemModelTest::testEditMetadata()
-{
-    int tempDirIndex = 2;
-    increaseIndexIfNeeded(tempDirIndex);
-
-    const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
-    QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted);
-
-    // create a new place
-    createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString());
-    QTRY_COMPARE(itemsInsertedSpy.count(), 1);
-
-    // check if the new entry was created
-    PlacesItemModel *model = new PlacesItemModel();
-    QTRY_COMPARE(model->count(), m_model->count());
-
-    // make sure that the new item will be removed later
-    schedulePlaceRemoval(tempDirIndex);
-
-    // modify place metadata
-    auto bookmark = m_model->placesItem(tempDirIndex)->bookmark();
-    bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), KAboutData::applicationData().componentName());
-    m_model->refresh();
-
-    // check if the place was modified in both models
-    QTRY_COMPARE(model->placesItem(tempDirIndex)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")),
-                 KAboutData::applicationData().componentName());
-    QTRY_COMPARE(model->placesItem(tempDirIndex)->text(), m_model->placesItem(tempDirIndex)->text());
-    QTRY_COMPARE(model->placesItem(tempDirIndex)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")),
-                 m_model->placesItem(tempDirIndex)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")));
-    QTRY_COMPARE(model->placesItem(tempDirIndex)->icon(), m_model->placesItem(tempDirIndex)->icon());
-    QTRY_COMPARE(model->placesItem(tempDirIndex)->url(), m_model->placesItem(tempDirIndex)->url());
-}
-
-void PlacesItemModelTest::testRefresh()
-{
-    int tempDirIndex = 2;
-    increaseIndexIfNeeded(tempDirIndex);
-
-    const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
-    QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted);
-
-    // create a new place
-    createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString());
-    QTRY_COMPARE(itemsInsertedSpy.count(), 1);
-
-    PlacesItemModel *model = new PlacesItemModel();
-    QTRY_COMPARE(model->count(), m_model->count());
-
-    // make sure that the new item will be removed later
-    schedulePlaceRemoval(tempDirIndex);
-
-    PlacesItem *item = m_model->placesItem(tempDirIndex);
-    PlacesItem *sameItem = model->placesItem(tempDirIndex);
-    QCOMPARE(item->text(), sameItem->text());
-
-    // modify place text
-    item->setText(QStringLiteral("Renamed place"));
-
-    // item from another model is not affected at the moment
-    QVERIFY(item->text() != sameItem->text());
-
-    // propagate change
-    m_model->refresh();
-
-    // item must be equal
-    QTRY_COMPARE(item->text(), sameItem->text());
-}
-
-void PlacesItemModelTest::testIcons_data()
-{
-    QTest::addColumn<QUrl>("url");
-    QTest::addColumn<QString>("expectedIconName");
-
-    // places
-    QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << QStringLiteral("user-home");
-
-    // baloo -search
-    QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << QStringLiteral("folder-text");
-
-    // devices
-    QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << QStringLiteral("blockdevice");
-}
-
-void PlacesItemModelTest::testIcons()
-{
-    QFETCH(QUrl, url);
-    QFETCH(QString, expectedIconName);
-
-    PlacesItem *item = m_model->placesItem(indexOf(url));
-    QCOMPARE(item->icon(), expectedIconName);
-
-    for (int r = 0; r < m_model->count(); r++) {
-        QVERIFY(!m_model->placesItem(r)->icon().isEmpty());
-    }
-}
-
-void PlacesItemModelTest::testDragAndDrop()
-{
-    int lastIndex = 1; // last index of places group
-    increaseIndexIfNeeded(lastIndex);
-
-    QList<QVariant> args;
-    KItemRangeList range;
-    QStringList urls = initialUrls();
-
-    QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted);
-    QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved);
-
-    CHECK_PLACES_URLS(initialUrls());
-    // Move the home directory to the end of the places group
-    QMimeData *dropData = createMimeData(QList<int>() << 0);
-    m_model->dropMimeDataBefore(m_model->count() - 1, dropData);
-    urls.move(0, lastIndex);
-    delete dropData;
-
-    QTRY_COMPARE(itemsInsertedSpy.count(), 1);
-    QTRY_COMPARE(itemsRemovedSpy.count(), 1);
-
-    // remove item from actual position
-    args = itemsRemovedSpy.takeFirst();
-    range = args.at(0).value<KItemRangeList>();
-    QCOMPARE(range.size(), 1);
-    QCOMPARE(range.at(0).count, 1);
-    QCOMPARE(range.at(0).index, 0);
-
-    // insert intem in his group
-    args = itemsInsertedSpy.takeFirst();
-    range = args.at(0).value<KItemRangeList>();
-    QCOMPARE(range.size(), 1);
-    QCOMPARE(range.at(0).count, 1);
-    QCOMPARE(range.at(0).index, lastIndex);
-
-    CHECK_PLACES_URLS(urls);
-
-    itemsInsertedSpy.clear();
-    itemsRemovedSpy.clear();
-
-    // Move home directory item back to its original position
-    dropData = createMimeData(QList<int>() << lastIndex);
-    m_model->dropMimeDataBefore(0, dropData);
-    urls.move(lastIndex, 0);
-    delete dropData;
-
-    QTRY_COMPARE(itemsInsertedSpy.count(), 1);
-    QTRY_COMPARE(itemsRemovedSpy.count(), 1);
-
-    // remove item from actual position
-    args = itemsRemovedSpy.takeFirst();
-    range = args.at(0).value<KItemRangeList>();
-    QCOMPARE(range.size(), 1);
-    QCOMPARE(range.at(0).count, 1);
-    QCOMPARE(range.at(0).index, lastIndex);
-
-    // insert intem in the requested position
-    args = itemsInsertedSpy.takeFirst();
-    range = args.at(0).value<KItemRangeList>();
-    QCOMPARE(range.size(), 1);
-    QCOMPARE(range.at(0).count, 1);
-    QCOMPARE(range.at(0).index, 0);
-
-    CHECK_PLACES_URLS(urls);
-}
-
-void PlacesItemModelTest::testHideDevices()
-{
-    QSignalSpy itemsRemoved(m_model, &PlacesItemModel::itemsRemoved);
-    QStringList urls = initialUrls();
-
-    m_model->setGroupHidden(KFilePlacesModel::RemovableDevicesType, true);
-    QTRY_VERIFY(m_model->isGroupHidden(KFilePlacesModel::RemovableDevicesType));
-    QTRY_COMPARE(itemsRemoved.count(), 3);
-
-    // remove removable-devices
-    urls.removeOne(QStringLiteral("/media/floppy0"));
-    urls.removeOne(QStringLiteral("/media/XO-Y4"));
-    urls.removeOne(QStringLiteral("/media/cdrom"));
-
-    // check if the correct urls was removed
-    CHECK_PLACES_URLS(urls);
-
-    delete m_model;
-    m_model = new PlacesItemModel();
-    QTRY_COMPARE(m_model->count(), urls.count());
-    CHECK_PLACES_URLS(urls);
-
-    // revert changes
-    m_model->setGroupHidden(KFilePlacesModel::RemovableDevicesType, false);
-    urls = initialUrls();
-    QTRY_COMPARE(m_model->count(), urls.count());
-    CHECK_PLACES_URLS(urls);
-}
-
-void PlacesItemModelTest::testDuplicatedEntries()
-{
-    QStringList urls = initialUrls();
-    // create a duplicated entry on bookmark
-    KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces"));
-    KBookmarkGroup root = bookmarkManager->root();
-    KBookmark bookmark = root.addBookmark(QStringLiteral("Duplicated Search Videos"), QUrl("search:/videos"), {});
-
-    const QString id = QUuid::createUuid().toString();
-    bookmark.setMetaDataItem(QStringLiteral("ID"), id);
-    bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), KAboutData::applicationData().componentName());
-    bookmarkManager->emitChanged(bookmarkManager->root());
-
-    PlacesItemModel *newModel = new PlacesItemModel();
-    QTRY_COMPARE(placesUrls(newModel).count(QStringLiteral("search:/videos")), 1);
-    QTRY_COMPARE(urls, placesUrls(newModel));
-    delete newModel;
-}
-
-void PlacesItemModelTest::renameAfterCreation()
-{
-    int tempDirIndex = 2;
-    increaseIndexIfNeeded(tempDirIndex);
-
-    const QUrl tempUrl = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
-    QStringList urls = initialUrls();
-    PlacesItemModel *model = new PlacesItemModel();
-
-    CHECK_PLACES_URLS(urls);
-    QTRY_COMPARE(model->count(), m_model->count());
-
-    // create a new place
-    createPlaceItem(QStringLiteral("Temporary Dir"), tempUrl, QString());
-    urls.insert(tempDirIndex, tempUrl.toLocalFile());
-
-    // make sure that the new item will be removed later
-    schedulePlaceRemoval(tempDirIndex);
-
-    CHECK_PLACES_URLS(urls);
-    QCOMPARE(model->count(), m_model->count());
-
-
-    // modify place text
-    QSignalSpy changedSpy(m_model, &PlacesItemModel::itemsChanged);
-
-    PlacesItem *item = m_model->placesItem(tempDirIndex);
-    item->setText(QStringLiteral("New Temporary Dir"));
-    item->setUrl(item->url());
-    item->setIcon(item->icon());
-    m_model->refresh();
-
-    QTRY_COMPARE(changedSpy.count(), 1);
-
-    // check if the place was modified in both models
-    QTRY_COMPARE(m_model->placesItem(tempDirIndex)->text(), QStringLiteral("New Temporary Dir"));
-    QTRY_COMPARE(model->placesItem(tempDirIndex)->text(), QStringLiteral("New Temporary Dir"));
-}
-
-QTEST_MAIN(PlacesItemModelTest)
-
-#include "placesitemmodeltest.moc"
index 51dbdbc580006f5a65e99e3f640b5a453e7cd987..5d75a5343baaef7175b75993f4217e7b3b608b39 100644 (file)
@@ -31,12 +31,12 @@ static void setTimeStamp(const QString& path, const QDateTime& mtime)
 {
 #ifdef Q_OS_UNIX
     struct utimbuf utbuf;
-    utbuf.actime = mtime.toTime_t();
+    utbuf.actime = mtime.toSecsSinceEpoch();
     utbuf.modtime = utbuf.actime;
     utime(QFile::encodeName(path), &utbuf);
 #elif defined(Q_OS_WIN)
     struct _utimbuf utbuf;
-    utbuf.actime = mtime.toTime_t();
+    utbuf.actime = mtime.toSecsSinceEpoch();
     utbuf.modtime = utbuf.actime;
     _wutime(reinterpret_cast<const wchar_t *>(path.utf16()), &utbuf);
 #endif
index df88345561b1268a44dca0281e570fac31713906..1446ab38812754fa6abe2f15e23c6d85a43b907a 100644 (file)
@@ -28,7 +28,7 @@ Trash::Trash()
         bool isTrashEmpty = m_trashDirLister->items().isEmpty();
         Q_EMIT emptinessChanged(isTrashEmpty);
     };
-    connect(m_trashDirLister, QOverload<>::of(&KCoreDirLister::completed), this, trashDirContentChanged);
+    connect(m_trashDirLister, &KCoreDirLister::completed, this, trashDirContentChanged);
     connect(m_trashDirLister, &KDirLister::itemsDeleted, this, trashDirContentChanged);
     m_trashDirLister->openUrl(QUrl(QStringLiteral("trash:/")));
 }
index 864d180c8ee3f38ee78146723b2bd398407f3316..92358924660c21c485ad44c91755052c5d599e3d 100644 (file)
@@ -73,6 +73,7 @@ void DolphinItemListView::readSettings()
     beginTransaction();
 
     setEnabledSelectionToggles(GeneralSettings::showSelectionToggle());
+    setHighlightEntireRow(DetailsModeSettings::leadingPadding());
     setSupportsItemExpanding(itemLayoutSupportsItemExpanding(itemLayout()));
 
     updateFont();
index 41b3b6890db29a2886737ae5b5cd80aaeb743066..c7c8b09d1f8d7205818bd69c7eae2beeab0247bd 100644 (file)
@@ -90,7 +90,7 @@ void DolphinRemoteEncoding::fillMenu()
     QMenu* menu = m_menu->menu();
     menu->clear();
 
-
+    menu->addAction(i18n("Default"), this, SLOT(slotDefault()), 0)->setCheckable(true);
     for (int i = 0; i < m_encodingDescriptions.size();i++) {
         QAction* action = new QAction(m_encodingDescriptions.at(i), this);
         action->setCheckable(true);
@@ -100,7 +100,6 @@ void DolphinRemoteEncoding::fillMenu()
     menu->addSeparator();
 
     menu->addAction(i18n("Reload"), this, SLOT(slotReload()), 0);
-    menu->addAction(i18n("Default"), this, SLOT(slotDefault()), 0)->setCheckable(true);
     m_idDefault = m_encodingDescriptions.size() + 2;
 
     connect(menu, &QMenu::triggered, this, &DolphinRemoteEncoding::slotItemSelected);
index 9a063d857f79dc07261af1d5012fb15c1db6dda4..5646fa9828ccabdcad33ffe746f682a58d2cb8bd 100644 (file)
@@ -8,6 +8,7 @@
 #include "dolphinview.h"
 
 #include "dolphin_generalsettings.h"
+#include "dolphin_detailsmodesettings.h"
 #include "dolphinitemlistview.h"
 #include "dolphinnewfilemenuobserver.h"
 #include "draganddrophelper.h"
@@ -59,6 +60,7 @@
 #include <QScrollBar>
 #include <QSize>
 #include <QTimer>
+#include <QToolTip>
 #include <QVBoxLayout>
 
 DolphinView::DolphinView(const QUrl& url, QWidget* parent) :
@@ -200,6 +202,8 @@ DolphinView::DolphinView(const QUrl& url, QWidget* parent) :
             this, &DolphinView::slotRoleEditingCanceled);
     connect(m_view->header(), &KItemListHeader::columnWidthChangeFinished,
             this, &DolphinView::slotHeaderColumnWidthChangeFinished);
+    connect(m_view->header(), &KItemListHeader::leadingPaddingChanged,
+            this, &DolphinView::slotLeadingPaddingWidthChanged);
 
     KItemListSelectionManager* selectionManager = controller->selectionManager();
     connect(selectionManager, &KItemListSelectionManager::selectionChanged,
@@ -918,6 +922,11 @@ bool DolphinView::eventFilter(QObject* watched, QEvent* event)
         if (watched == m_view) {
             m_dragging = false;
         }
+        break;
+
+    case QEvent::ToolTip:
+        tryShowNameToolTip(event);
+
     default:
         break;
     }
@@ -974,12 +983,14 @@ void DolphinView::slotItemActivated(int index)
     }
 }
 
-void DolphinView::slotItemsActivated(const KItemSetindexes)
+void DolphinView::slotItemsActivated(const KItemSet &indexes)
 {
     Q_ASSERT(indexes.count() >= 2);
 
     abortTwoClicksRenaming();
 
+    const auto modifiers = QGuiApplication::keyboardModifiers();
+
     if (indexes.count() > 5) {
         QString question = i18np("Are you sure you want to open 1 item?", "Are you sure you want to open %1 items?", indexes.count());
         const int answer = KMessageBox::warningYesNo(this, question);
@@ -995,8 +1006,15 @@ void DolphinView::slotItemsActivated(const KItemSet& indexes)
         KFileItem item = m_model->fileItem(index);
         const QUrl& url = openItemAsFolderUrl(item);
 
-        if (!url.isEmpty()) { // Open folders in new tabs
-            Q_EMIT tabRequested(url);
+        if (!url.isEmpty()) {
+            // Open folders in new tabs or in new windows depending on the modifier
+            // The ctrl+shift behavior is ignored because we are handling multiple items
+            // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
+            if (modifiers & Qt::ShiftModifier && !(modifiers & Qt::ControlModifier)) {
+                Q_EMIT windowRequested(url);
+            } else {
+                Q_EMIT tabRequested(url);
+            }
         } else {
             items.append(item);
         }
@@ -1013,10 +1031,21 @@ void DolphinView::slotItemMiddleClicked(int index)
 {
     const KFileItem& item = m_model->fileItem(index);
     const QUrl& url = openItemAsFolderUrl(item);
+    const auto modifiers = QGuiApplication::keyboardModifiers();
     if (!url.isEmpty()) {
-        Q_EMIT tabRequested(url);
+        // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
+        if (modifiers & Qt::ShiftModifier) {
+            Q_EMIT activeTabRequested(url);
+        } else {
+            Q_EMIT tabRequested(url);
+        }
     } else if (isTabsForFilesEnabled()) {
-        Q_EMIT tabRequested(item.url());
+        // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
+        if (modifiers & Qt::ShiftModifier) {
+            Q_EMIT activeTabRequested(item.url());
+        } else {
+            Q_EMIT tabRequested(item.url());
+        }
     }
 }
 
@@ -1091,6 +1120,10 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos)
     QActionGroup* widthsGroup = new QActionGroup(menu);
     const bool autoColumnWidths = props.headerColumnWidths().isEmpty();
 
+    QAction* toggleLeadingPaddingAction = menu->addAction(i18nc("@action:inmenu", "Leading Column Padding"));
+    toggleLeadingPaddingAction->setCheckable(true);
+    toggleLeadingPaddingAction->setChecked(view->header()->leadingPadding() > 0);
+
     QAction* autoAdjustWidthsAction = menu->addAction(i18nc("@action:inmenu", "Automatic Column Widths"));
     autoAdjustWidthsAction->setCheckable(true);
     autoAdjustWidthsAction->setChecked(autoColumnWidths);
@@ -1121,6 +1154,8 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos)
             }
             props.setHeaderColumnWidths(columnWidths);
             header->setAutomaticColumnResizing(false);
+        } else if (action == toggleLeadingPaddingAction) {
+            header->setLeadingPadding(toggleLeadingPaddingAction->isChecked() ? 20 : 0);
         } else {
             // Show or hide the selected role
             const QByteArray selectedRole = action->data().toByteArray();
@@ -1173,6 +1208,13 @@ void DolphinView::slotHeaderColumnWidthChangeFinished(const QByteArray& role, qr
     props.setHeaderColumnWidths(columnWidths);
 }
 
+void DolphinView::slotLeadingPaddingWidthChanged(qreal width)
+{
+    ViewProperties props(viewPropertiesUrl());
+    DetailsModeSettings::setLeadingPadding(int(width));
+    m_view->writeSettings();
+}
+
 void DolphinView::slotItemHovered(int index)
 {
     const KFileItem item = m_model->fileItem(index);
@@ -1617,13 +1659,15 @@ void DolphinView::updateViewState()
 
 void DolphinView::hideToolTip(const ToolTipManager::HideBehavior behavior)
 {
-#ifdef HAVE_BALOO
     if (GeneralSettings::showToolTips()) {
+#ifdef HAVE_BALOO
         m_toolTipManager->hideToolTip(behavior);
-    }
 #else
         Q_UNUSED(behavior)
 #endif
+    } else if (m_mode == DolphinView::IconsView) {
+       QToolTip::hideText();
+    }
 }
 
 void DolphinView::slotTwoClicksRenamingTimerTimeout()
@@ -1964,6 +2008,7 @@ void DolphinView::applyViewProperties(const ViewProperties& props)
         } else {
             header->setAutomaticColumnResizing(true);
         }
+        header->setLeadingPadding(DetailsModeSettings::leadingPadding());
     }
 
     m_view->endTransaction();
@@ -2121,7 +2166,13 @@ void DolphinView::updatePlaceholderLabel()
     } else if (m_url.scheme() == QLatin1String("trash") && m_url.path() == QLatin1String("/")) {
         m_placeholderLabel->setText(i18n("Trash is empty"));
     } else if (m_url.scheme() == QLatin1String("tags")) {
-        m_placeholderLabel->setText(i18n("No tags"));
+        if (m_url.path() == QLatin1Char('/')) {
+            m_placeholderLabel->setText(i18n("No tags"));
+        } else {
+            const QString tagName = m_url.path().mid(1); // Remove leading /
+            m_placeholderLabel->setText(i18n("No files tagged with \"%1\"", tagName));
+        }
+
     } else if (m_url.scheme() == QLatin1String("recentlyused")) {
         m_placeholderLabel->setText(i18n("No recently used items"));
     } else if (m_url.scheme() == QLatin1String("smb")) {
@@ -2138,3 +2189,25 @@ void DolphinView::updatePlaceholderLabel()
 
     m_placeholderLabel->setVisible(true);
 }
+
+void DolphinView::tryShowNameToolTip(QEvent* event)
+{
+    if (!GeneralSettings::showToolTips() && m_mode == DolphinView::IconsView) {
+        QHelpEvent *hoverEvent = reinterpret_cast<QHelpEvent *>(event);
+        const std::optional<int> index = m_view->itemAt(hoverEvent->pos());
+
+        if (!index.has_value()) {
+            return;
+        }
+
+        // Check whether the filename has been elided
+        const bool isElided = m_view->isElided(index.value());
+
+        if(isElided) {
+            const KFileItem item = m_model->fileItem(index.value());
+            const QString text = item.text();
+            const QPoint pos = mapToGlobal(hoverEvent->pos());
+            QToolTip::showText(pos, text);
+        }
+    }
+}
index 75c9dd9856a382b9f9e2126b1ad2b66d4218424e..e93ca4fa026bebe041ad80791ad55d8d690ffdcf 100644 (file)
@@ -425,13 +425,13 @@ Q_SIGNALS:
     /**
      * Is emitted when clicking on an item with the left mouse button.
      */
-    void itemActivated(const KFileItemitem);
+    void itemActivated(const KFileItem &item);
 
     /**
      * Is emitted when multiple items have been activated by e. g.
      * context menu open with.
      */
-    void itemsActivated(const KFileItemListitems);
+    void itemsActivated(const KFileItemList &items);
 
     /**
      * Is emitted if items have been added or deleted.
@@ -443,6 +443,16 @@ Q_SIGNALS:
      */
     void tabRequested(const QUrl& url);
 
+    /**
+     * Is emitted if a new tab should be opened for the URL \a url and set as active.
+     */
+    void activeTabRequested(const QUrl &url);
+
+    /**
+     * Is emitted if a new window should be opened for the URL \a url.
+     */
+    void windowRequested(const QUrl &url);
+
     /**
      * Is emitted if the view mode (IconsView, DetailsView,
      * PreviewsView) has been changed.
@@ -458,7 +468,7 @@ Q_SIGNALS:
     /** Is emitted if the 'grouped sorting' property has been changed. */
     void groupedSortingChanged(bool groupedSorting);
 
-    /** Is emmited in reaction to a requestStatusBarText() call.
+    /** Is emitted in reaction to a requestStatusBarText() call.
      * @see requestStatusBarText() */
     void statusBarTextChanged(QString statusBarText);
 
@@ -619,12 +629,13 @@ private Q_SLOTS:
     void activate();
 
     void slotItemActivated(int index);
-    void slotItemsActivated(const KItemSetindexes);
+    void slotItemsActivated(const KItemSet &indexes);
     void slotItemMiddleClicked(int index);
     void slotItemContextMenuRequested(int index, const QPointF& pos);
     void slotViewContextMenuRequested(const QPointF& pos);
     void slotHeaderContextMenuRequested(const QPointF& pos);
     void slotHeaderColumnWidthChangeFinished(const QByteArray& role, qreal current);
+    void slotLeadingPaddingWidthChanged(qreal width);
     void slotItemHovered(int index);
     void slotItemUnhovered(int index);
     void slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event);
@@ -852,6 +863,8 @@ private:
 
     void updatePlaceholderLabel();
 
+    void tryShowNameToolTip(QEvent* event);
+
 private:
     void updatePalette();
     void showLoadingPlaceholder();
index 47247ec354ebb074251f783a947a054388a6a00d..2e524f8f224f438e011e4cc3984d7f30359c71cd 100644 (file)
@@ -213,7 +213,7 @@ void DolphinViewActionHandler::createActions()
     viewModeActions->addAction(compactAction);
     viewModeActions->addAction(detailsAction);
     viewModeActions->setToolBarMode(KSelectAction::MenuMode);
-    connect(viewModeActions, QOverload<QAction*>::of(&KSelectAction::triggered), this, &DolphinViewActionHandler::slotViewModeActionTriggered);
+    connect(viewModeActions, &KSelectAction::triggered, this, &DolphinViewActionHandler::slotViewModeActionTriggered);
 
     QAction* zoomInAction = KStandardAction::zoomIn(this,
                             &DolphinViewActionHandler::zoomIn,
index b147135bfdc59ced81ef960574b068bfaaa809fc..e914593fb5f9898e1096403c6b20bcd49a275a36 100644 (file)
@@ -66,7 +66,7 @@ DolphinFileMetaDataWidget::DolphinFileMetaDataWidget(QWidget* parent) :
 
     QHBoxLayout* layout = new QHBoxLayout(this);
     layout->addWidget(m_preview);
-    layout->addSpacing(layout->margin());
+    layout->addSpacing(layout->contentsMargins().left());
     layout->addLayout(textLayout);
 }
 
index 4786445f1a98563214b1709e3123e79ffd800944..54af9c94cf9e16e98a5efb8b8d0eaf82a9b23996 100644 (file)
@@ -139,7 +139,7 @@ void ToolTipManager::startContentRetrieval()
     KIO::PreviewJob* job = new KIO::PreviewJob(KFileItemList() << m_item,
                                                QSize(256, 256),
                                                &plugins);
-    job->setIgnoreMaximumSize(m_item.isLocalFile());
+    job->setIgnoreMaximumSize(m_item.isLocalFile() && !m_item.isSlow());
     if (job->uiDelegate()) {
         KJobWidgets::setWindow(job, qApp->activeWindow());
     }
index c908be24720288b4837cb4200b72070135df48b0..d3a39fbd6b0e43ce190b0ebc0899a03ec53f8c94 100644 (file)
@@ -45,7 +45,6 @@ class KFileItem;
  * - Add the following lines at the top of fileviewsvnplugin.cpp:
  *   <code>
  *   #include <KPluginFactory>
- *   #include <KPluginLoader>
  *   K_PLUGIN_CLASS_WITH_JSON(FileViewSvnPlugin, "fileviewsvnplugin.json")
  *   </code>
  *
@@ -143,8 +142,8 @@ public:
     virtual QString fileName() const = 0;
 
     /**
-     * Returns the path of the local repository root for the versionned directory
-     * Returns an emtpy QString when directory is not part of a working copy
+     * Returns the path of the local repository root for the versioned directory
+     * Returns an empty QString when directory is not part of a working copy
      */
     virtual QString localRepositoryRoot(const QString& directory) const;
 
index 6766aa479a0b57dd2c7454ec40d6ddc2d0dedc88..a773aef6b397be9e4e76f5a1075a6e585e12abc0 100644 (file)
@@ -14,7 +14,6 @@
 
 #include <KLocalizedString>
 #include <KPluginFactory>
-#include <KPluginLoader>
 #include <KPluginMetaData>
 
 #include <QTimer>
@@ -136,7 +135,7 @@ void VersionControlObserver::slotItemsChanged(const KItemRangeList& itemRanges,
 {
     Q_UNUSED(itemRanges)
 
-    // Because "version" role is emitted by VCS plugin (ourselfs) we don't need to
+    // Because "version" role is emitted by VCS plugin (ourselves) we don't need to
     // analyze it and update directory item states information. So lets check if
     // there is only "version".
     if ( !(roles.count() == 1 && roles.contains("version")) ) {
@@ -164,7 +163,7 @@ void VersionControlObserver::verifyDirectory()
             // by an immediate verification.
             m_dirVerificationTimer->setInterval(500);
         } else {
-            // View was versionned but should not be anymore
+            // View was versioned but should not be anymore
             updateItemStates();
         }
     } else if ((m_plugin = searchPlugin(rootItem.url()))) {
@@ -280,15 +279,13 @@ void VersionControlObserver::initPlugins()
         // all fileview version control plugins and remember them in 'plugins'.
         const QStringList enabledPlugins = VersionControlSettings::enabledPlugins();
 
-        const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("dolphin/vcs"));
+        const QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("dolphin/vcs"));
 
         QSet<QString> loadedPlugins;
 
         for (const auto &p : plugins) {
             if (enabledPlugins.contains(p.name())) {
-                KPluginLoader loader(p.fileName());
-                KPluginFactory *factory = loader.factory();
-                KVersionControlPlugin *plugin = factory->create<KVersionControlPlugin>();
+                auto plugin = KPluginFactory::instantiatePlugin<KVersionControlPlugin>(p).plugin;
                 if (plugin) {
                     m_plugins.append(plugin);
                     loadedPlugins += p.name();
index 6e3977fb2a6d1f6fdb4e61709363c300014d9252..064a3088fc27bbbba587b6499180950bdc6dd3bf 100644 (file)
@@ -95,7 +95,7 @@ private Q_SLOTS:
 
     /**
      * Is invoked if the thread m_updateItemStatesThread has been finished
-     * and applys the item states.
+     * and applies the item states.
      */
     void slotThreadFinished();