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
'frameworks/baloo': '@stable'
'frameworks/kwindowsystem': '@stable'
'frameworks/kfilemetadata': '@stable'
- 'libraries/baloo-widgets': '@stable'
+ 'libraries/baloo-widgets': '@same'
'libraries/kuserfeedback': '@stable'
'libraries/phonon': '@stable'
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)
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"
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
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
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)
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
include(CMakeFindDependencyMacro)
-find_dependency(Qt5Widgets)
+find_dependency(Qt@QT_MAJOR_VERSION@Widgets)
find_dependency(KF5KIO)
include("${CMAKE_CURRENT_LIST_DIR}/DolphinVcsTargets.cmake")
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}
)
<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>
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
target_link_libraries(
dolphinvcs PUBLIC
- Qt5::Widgets
+ Qt${QT_MAJOR_VERSION}::Widgets
)
set_target_properties(dolphinvcs PROPERTIES
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)
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
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
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})
##########################################
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
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}
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})
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 )
DESTINATION ${KDE_INSTALL_KCFGDIR} )
if(BUILD_TESTING)
- find_package(Qt5Test ${QT_MIN_VERSION} CONFIG REQUIRED)
add_subdirectory(tests)
endif()
#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"
// 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());
// 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());
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()
#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"
#include <KProtocolInfo>
#include <KProtocolManager>
#include <KShell>
+#include <KShortcutsDialog>
#include <KStandardAction>
#include <KStartupInfo>
#include <KSycoca>
#include <KUrlNavigator>
#include <KWindowSystem>
#include <KXMLGUIFactory>
-#include <kxmlgui_version.h>
#include <kio_version.h>
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);
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);
setupDockWidgets();
- setupGUI(Keys | Save | Create | ToolBar);
+ setupGUI(Save | Create | ToolBar);
stateChanged(QStringLiteral("new_file"));
QClipboard* clipboard = QApplication::clipboard();
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);
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);
}
}
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);
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();
}
}
}
+QMenu *DolphinMainWindow::createPopupMenu()
+{
+ QMenu *menu = KXmlGuiWindow::createPopupMenu();
+
+ menu->addSeparator();
+ menu->addAction(actionCollection()->action(QStringLiteral("lock_panels")));
+
+ return menu;
+}
+
void DolphinMainWindow::updateHamburgerMenu()
{
KActionCollection* ac = actionCollection();
}
}
+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();
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. "
"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.
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);
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,
"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 "
panelsMenu->addAction(lockLayoutAction);
connect(panelsMenu->menu(), &QMenu::aboutToShow, this, [actionShowAllPlaces, this]{
- actionShowAllPlaces->setEnabled(m_placesPanel->hiddenListCount());
+ actionShowAllPlaces->setEnabled(DolphinPlacesModelSingleton::instance().placesModel()->hiddenCount());
});
}
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);
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,
this, &DolphinMainWindow::slotEditableStateChanged);
connect(navigator, &KUrlNavigator::tabRequested,
this, &DolphinMainWindow::openNewTab);
+ connect(navigator, &KUrlNavigator::activeTabRequested,
+ this, &DolphinMainWindow::openNewTabAndActivate);
+ connect(navigator, &KUrlNavigator::newWindowRequested,
+ this, &DolphinMainWindow::openNewWindow);
}
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);
*/
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
*/
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();
* 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.
* 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();
* 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;
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);
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,
// 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()
{
// 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());
}
void DolphinPart::createDirectory()
{
- m_newFileMenu->setViewShowsHiddenFiles(m_view->hiddenFilesShown());
m_newFileMenu->setPopupFiles(QList<QUrl>() << url());
m_newFileMenu->createDirectory();
}
*/
#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()))
{
}
#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.
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);
}
const int index = tabAt(event->pos());
if (mimeData->hasUrls()) {
+ if (index >= 0) {
+ event->acceptProposedAction();
+ } else {
+ event->setDropAction(Qt::IgnoreAction);
+ }
updateAutoActivationTimer(index);
}
*
* @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
private:
/**
- * @return wether the places selector of DolphinUrlNavigators should be visible.
+ * @return whether the places selector of DolphinUrlNavigators should be visible.
*/
static bool placesSelectorVisible();
#include <KUrlComboBox>
#include <QDropEvent>
+#include <QGuiApplication>
#include <QLoggingCategory>
#include <QMimeData>
#include <QTimer>
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);
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();
}
}
-void DolphinViewContainer::slotItemActivated(const KFileItem& item)
+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
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();
}
Q_ASSERT(items.count() >= 2);
KFileItemActions fileItemActions(this);
- fileItemActions.runPreferredApplications(items, QString());
+ fileItemActions.runPreferredApplications(items);
}
void DolphinViewContainer::showItemInfo(const KFileItem& item)
}
}
+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"));
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();
*/
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
* directory is opened in the view. If the item is a file, the file
* gets started by the corresponding application.
*/
- void slotItemActivated(const KFileItem& item);
+ void slotItemActivated(const KFileItem &item);
/**
* Handles activation of multiple files. The files get started by
*/
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://).
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);
}
return font;
}
-
KFileItemListWidget::KFileItemListWidget(KItemListWidgetInformant* informant, QGraphicsItem* parent) :
KStandardItemListWidget(informant, parent)
{
#include <KDirLister>
#include <KIO/Job>
#include <KLocalizedString>
+#include <KLazyLocalizedString>
#include <KUrlMimeData>
#include <QElapsedTimer>
#include <QWidget>
#include <QRecursiveMutex>
#include <QIcon>
+#include <algorithm>
+#include <klazylocalizedstring.h>
Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex)
}
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
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;
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());
}
}
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
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
{
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
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()) {
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();
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);
}
QList<ItemData*> newVisibleItems;
QListIterator<QPair<KFileItem, KFileItem> > it(items);
+
while (it.hasNext()) {
const QPair<KFileItem, KFileItem>& itemPair = it.next();
const KFileItem& oldItem = itemPair.first;
}
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 {
// 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) {
}
}
- // 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);
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);
}
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());
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();
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);
#include "kitemviews/private/kfileitemmodelfilter.h"
#include <KFileItem>
+#include <KLazyLocalizedString>
#include <QCollator>
#include <QHash>
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:
enum RemoveItemsBehavior {
KeepItemData,
- DeleteItemData
+ DeleteItemData,
+ DeleteItemDataIfUnfiltered
};
void insertItems(QList<ItemData*>& items);
{
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;
};
*/
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;
#include <KIconLoader>
#include <KJobWidgets>
#include <KOverlayIconPlugin>
-#include <KPluginLoader>
+#include <KPluginMetaData>
#include <KSharedConfig>
#ifdef HAVE_BALOO
#include <QApplication>
#include <QIcon>
#include <QPainter>
+#include <QPluginLoader>
#include <QElapsedTimer>
#include <QTimer>
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;
}
}
}
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());
}
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());
}
(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.
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),
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.
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) {
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);
}
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())) {
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;
}
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);
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;
}
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();
}
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();
}
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);
}
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;
}
}
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
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
// - 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.
// 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));
}
}
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()) {
}
}
- 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;
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;
}
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;
}
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;
}
#ifndef KITEMLISTCONTROLLER_H
#define KITEMLISTCONTROLLER_H
+#include <optional>
+
#include "dolphin_export.h"
#include "kitemset.h"
* Is emitted if more than one item has been activated by pressing Return/Enter
* when having a selection.
*/
- void itemsActivated(const KItemSet& indexes);
+ void itemsActivated(const KItemSet &indexes);
void itemMiddleClicked(int index);
KItemListView* m_view;
KItemListSelectionManager* m_selectionManager;
KItemListKeyboardSearchManager* m_keyboardManager;
- int m_pressedIndex;
+ std::optional<int> m_pressedIndex;
QPointF m_pressedMousePos;
QTimer* m_autoActivationTimer;
if (event->oldSize().height() != event->newSize().height()) {
m_dirtyCache = true;
}
+ updateSize();
}
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);
size().width() - 2 * padding - horizontalMargin,
roleHeight);
- m_dirtyCache = false;
+ update();
}
QColor KItemListGroupHeader::mixedColor(const QColor& c1, const QColor& c2, int c1Percent)
private:
void updateCache();
+ void updateSize();
static QColor mixedColor(const QColor& c1, const QColor& c2, int c1Percent = 50);
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)
this, &KItemListHeader::columnWidthChanged);
connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChangeFinished,
this, &KItemListHeader::columnWidthChangeFinished);
+ connect(m_headerWidget, &KItemListHeaderWidget::leadingPaddingChanged,
+ this, &KItemListHeader::leadingPaddingChanged);
}
*/
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).
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()) {
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
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);
}
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);
return contextRect;
}
+bool KItemListView::isElided(int index) const
+{
+ return m_sizeHintResolver->isElided(index);
+}
+
void KItemListView::scrollToItem(int index)
{
QRectF viewGeometry = geometry();
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,
} 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,
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()));
Q_UNUSED(previous)
}
+void KItemListView::onHighlightEntireRowChanged(bool highlightEntireRow)
+{
+ Q_UNUSED(highlightEntireRow)
+}
+
void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
{
Q_UNUSED(supportsExpanding)
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)
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
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;
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);
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)
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
#ifndef KITEMLISTVIEW_H
#define KITEMLISTVIEW_H
+#include <optional>
+
#include "dolphin_export.h"
#include "kitemviews/kitemliststyleoption.h"
#include "kitemviews/kitemlistwidget.h"
* @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;
* @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
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()).
*/
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.
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();
virtual void updateFont();
virtual void updatePalette();
+ KItemListSizeHintResolver* m_sizeHintResolver;
+
protected Q_SLOTS:
virtual void slotItemsInserted(const KItemRangeList& itemRanges);
virtual void slotItemsRemoved(const KItemRangeList& itemRanges);
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.
/**
* Resizes the column-widths of m_headerWidget based on the preferred widths
- * and the vailable view-size.
+ * and the available view-size.
*/
void applyAutomaticColumnWidths();
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()
QHash<int, Cell> m_visibleCells;
int m_scrollBarExtent;
- KItemListSizeHintResolver* m_sizeHintResolver;
KItemListViewLayouter* m_layouter;
KItemListViewAnimation* m_animation;
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,
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,
}
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);
}
* @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.
*/
*/
#ifndef QT_NO_ACCESSIBILITY
-
#include "kitemlistviewaccessible.h"
#include "kitemlistcontainer.h"
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
m_selected(false),
m_current(false),
m_hovered(false),
+ m_expansionAreaHovered(false),
m_alternateBackground(false),
m_enabledSelectionToggle(false),
m_data(),
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) {
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) {
Q_UNUSED(previous)
}
+void KItemListWidget::leadingPaddingChanged(qreal width)
+{
+ Q_UNUSED(width)
+}
+
void KItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
const KItemListStyleOption& previous)
{
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,
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;
void setHovered(bool hovered);
bool isHovered() const;
+ void setExpansionAreaHovered(bool hover);
+ bool expansionAreaHovered() const;
+
void setHoverPosition(const QPointF& pos);
void setAlternateBackground(bool enable);
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);
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.
private:
void initializeSelectionToggle();
void setHoverOpacity(qreal opacity);
- void clearHoverCache();
void drawItemStyleOption(QPainter* painter, QWidget* widget, QStyle::State styleState);
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;
+++ /dev/null
-/*
- * 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)
-}
-
+++ /dev/null
-/*
- * 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
-
-
m_text(),
m_pixmap()
{
- m_text.setTextFormat(Qt::PlainText);
- m_text.setPerformanceHint(QStaticText::AggressiveCaching);
+
}
KStandardItemListGroupHeader::~KStandardItemListGroupHeader()
{
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);
}
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);
+ }
}
}
void KStandardItemListGroupHeader::resizeEvent(QGraphicsSceneResizeEvent* event)
{
- QGraphicsWidget::resizeEvent(event);
+ KItemListGroupHeader::resizeEvent(event);
m_dirtyCache = true;
}
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);
QFontMetricsF fontMetrics(font());
const QString text = fontMetrics.elidedText(data().toString(), Qt::ElideRight, maxWidth);
- m_text.setText(text);
+ m_text = text;
}
}
private:
bool m_dirtyCache;
- QStaticText m_text;
+ QString m_text;
QPixmap m_pixmap;
};
#endif
setAcceptDrops(true);
setScrollOrientation(Qt::Vertical);
setVisibleRoles({"text"});
+ setAlternateBackgrounds(true);
}
KStandardItemListView::~KStandardItemListView()
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);
default: Q_ASSERT(false); break;
}
+ standardItemListWidget->setHighlightEntireRow(highlightEntireRow());
standardItemListWidget->setSupportsItemExpanding(supportsItemExpanding());
}
{
}
-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:
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;
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;
}
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);
layout.beginLayout();
QTextLine line;
int lineCount = 0;
+ bool isElided = false;
while ((line = layout.createLine()).isValid()) {
line.setLineWidth(maxWidth);
line.naturalTextWidth();
++lineCount;
if (lineCount == option.maxTextLines) {
+ isElided = layout.createLine().isValid();
break;
}
}
// 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;
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;
}
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;
}
m_pixmapPos(),
m_pixmap(),
m_scaledPixmapSize(),
+ m_columnWidthSum(),
m_iconRect(),
m_hoverPixmap(),
m_textRect(),
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) {
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:
m_dirtyLayout = true;
}
+void KStandardItemListWidget::leadingPaddingChanged(qreal padding) {
+ Q_UNUSED(padding)
+ m_dirtyLayout = true;
+}
+
void KStandardItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
const KItemListStyleOption& previous)
{
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;
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;
}
}
if (m_supportsItemExpanding) {
firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2;
} else {
- firstColumnInc += option.padding;
+ firstColumnInc += option.padding + leadingPadding();
}
qreal x = firstColumnInc;
const bool isTextRole = (role == "text");
if (isTextRole) {
- availableTextWidth -= firstColumnInc;
+ availableTextWidth -= firstColumnInc - leadingPadding();
}
if (requiredWidth > availableTextWidth) {
// 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;
}
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();
}
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) {
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);
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
*/
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()
};
void setLayout(Layout layout);
Layout layout() const;
+ void setHighlightEntireRow(bool highlightEntireRow);
+ bool highlightEntireRow() const;
+
void setSupportsItemExpanding(bool supportsItemExpanding);
bool supportsItemExpanding() const;
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;
QFont m_customizedFont;
QFontMetrics m_customizedFontMetrics;
bool m_isExpandable;
+ bool m_highlightEntireRow;
bool m_supportsItemExpanding;
bool m_dirtyLayout;
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
+++ /dev/null
-/*
- * 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)
-}
-
+++ /dev/null
-/*
- * 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
-
-
#include <QCollator>
#include <QDebug>
+#include <QSize>
#include <QTime>
namespace {
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;
for (const auto& role : propertyRoleMap()) {
m_roles.insert(role);
}
+ m_roles.insert("dimensions");
// Display roles provided by UserMetaData
m_roles.insert(QByteArrayLiteral("tags"));
m_automaticColumnResizing(true),
m_model(nullptr),
m_offset(0),
+ m_leadingPadding(0),
m_columns(),
m_columnWidths(),
m_preferredColumnWidths(),
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());
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);
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();
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;
} 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);
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
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();
// 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()) {
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;
}
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)
{
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);
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);
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];
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);
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;
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;
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.
void slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous);
private:
+
+ enum PaddingGrip
+ {
+ Leading,
+ Trailing,
+ };
+
void paintRole(QPainter* painter,
const QByteArray& role,
const QRectF& rect,
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
{
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;
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);
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)
// 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;
// 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;
}
}
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;
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) {
{
Q_UNUSED(roles)
while (count) {
- m_logicalHeightHintCache[index] = 0.0;
+ m_logicalHeightHintCache[index] = std::make_pair(0.0, false);
++index;
--count;
}
void KItemListSizeHintResolver::clearCache()
{
- m_logicalHeightHintCache.fill(0.0);
+ m_logicalHeightHintCache.fill(std::make_pair(0.0, false));
m_needsResolving = true;
}
virtual ~KItemListSizeHintResolver();
QSizeF minSizeHint();
QSizeF sizeHint(int index);
+ bool isElided(int index);
void itemsInserted(const KItemRangeList& itemRanges);
void itemsRemoved(const KItemRangeList& itemRanges);
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;
#include "kitemlistsizehintresolver.h"
#include "kitemviews/kitemmodelbase.h"
+#include <QGuiApplication>
+#include <QScopeGuard>
+
// #define KITEMLISTVIEWLAYOUTER_DEBUG
KItemListViewLayouter::KItemListViewLayouter(KItemListSizeHintResolver* sizeHintResolver, QObject* parent) :
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()
// 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;
}
}
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();
<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>
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
{
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());
+ }
}
}
{
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());
+ }
}
}
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:
#include <Baloo/FileMetaDataWidget>
-#include <panels/places/placesitem.h>
-#include <panels/places/placesitemmodel.h>
-
#include <Phonon/BackendCapabilities>
#include <Phonon/MediaObject>
m_nameLabel(nullptr),
m_metaDataWidget(nullptr),
m_metaDataArea(nullptr),
- m_placesItemModel(nullptr),
m_isVideo(false)
{
parent->installEventFilter(this);
layout->addWidget(m_configureButtons);
grabGesture(Qt::TapAndHoldGesture);
-
- m_placesItemModel = new PlacesItemModel(this);
}
InformationPanelContent::~InformationPanelContent()
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);
}
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;
class KFileItemList;
class PhononWidget;
class PixmapViewer;
-class PlacesItemModel;
class QPixmap;
class QDialogButtonBox;
class QString;
QLabel* m_configureLabel;
QDialogButtonBox* m_configureButtons;
- PlacesItemModel* m_placesItemModel;
bool m_isVideo;
};
<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>
+++ /dev/null
-/*
- * 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();
-}
+++ /dev/null
-/*
- * 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
-
-
+++ /dev/null
-/*
- * 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;
-}
-
+++ /dev/null
-/*
- * 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
-
-
+++ /dev/null
-/*
- * 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();
-}
+++ /dev/null
-/*
- * 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
-
-
+++ /dev/null
-/*
- * 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;
-}
-
+++ /dev/null
-/*
- * 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
-
-
+++ /dev/null
-/*
- * 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"));
- }
-}
-
+++ /dev/null
-/*
- * 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
/*
* 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)
}
}
-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);
}
/*
* 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
+++ /dev/null
-/*
- * 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());
-}
+++ /dev/null
-/*
- * 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
#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>
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);
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);
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. "
#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;
bool terminalHasFocus() const;
bool hasProgramRunning() const;
QString runningProgramName() const;
+ KActionCollection *actionCollection();
public Q_SLOTS:
void terminalExited();
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()
#include "dolphin_searchsettings.h"
#include "dolphinfacetswidget.h"
+#include "dolphinplacesmodelsingleton.h"
#include "dolphinquery.h"
-#include "panels/places/placesitemmodel.h"
#include <KLocalizedString>
#include <KNS3/KMoreToolsMenuFactory>
{
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"));
}
}
#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>
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");
#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();
// 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"),
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);
}
[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
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
)
<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>
</entry>
<entry name="MaximumTextLines" type="Int">
<label>Maximum textlines (0 means unlimited)</label>
- <default>0</default>
+ <default>3</default>
</entry>
</group>
</kcfg>
});
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
#include <KIO/ThumbCreator>
#include <KJobWidgets>
#include <KLocalizedString>
-#include <KPluginLoader>
+#include <QPluginLoader>
#include <QDialogButtonBox>
#include <QPushButton>
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) {
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()
{
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);
{
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()
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);
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);
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());
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);
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
void testNameFilter();
void testEmptyPath();
void testRefreshExpandedItem();
+ void testAddItemToFilteredExpandedFolder();
+ void testDeleteItemsWithExpandedFolderWithFilter();
+ void testRefreshItemsWithFilter();
+ void testRefreshExpandedFolderWithFilter();
void testRemoveHiddenItems();
void collapseParentOfHiddenItems();
void removeParentOfHiddenItems();
// 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;
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
// 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());
+++ /dev/null
-/*
- * 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"
+++ /dev/null
-/*
- * 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"
{
#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
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:/")));
}
beginTransaction();
setEnabledSelectionToggles(GeneralSettings::showSelectionToggle());
+ setHighlightEntireRow(DetailsModeSettings::leadingPadding());
setSupportsItemExpanding(itemLayoutSupportsItemExpanding(itemLayout()));
updateFont();
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);
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);
#include "dolphinview.h"
#include "dolphin_generalsettings.h"
+#include "dolphin_detailsmodesettings.h"
#include "dolphinitemlistview.h"
#include "dolphinnewfilemenuobserver.h"
#include "draganddrophelper.h"
#include <QScrollBar>
#include <QSize>
#include <QTimer>
+#include <QToolTip>
#include <QVBoxLayout>
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,
if (watched == m_view) {
m_dragging = false;
}
+ break;
+
+ case QEvent::ToolTip:
+ tryShowNameToolTip(event);
+
default:
break;
}
}
}
-void DolphinView::slotItemsActivated(const KItemSet& indexes)
+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);
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);
}
{
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());
+ }
}
}
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);
}
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();
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);
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()
} else {
header->setAutomaticColumnResizing(true);
}
+ header->setLeadingPadding(DetailsModeSettings::leadingPadding());
}
m_view->endTransaction();
} 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")) {
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);
+ }
+ }
+}
/**
* Is emitted when clicking on an item with the left mouse button.
*/
- void itemActivated(const KFileItem& item);
+ void itemActivated(const KFileItem &item);
/**
* Is emitted when multiple items have been activated by e. g.
* context menu open with.
*/
- void itemsActivated(const KFileItemList& items);
+ void itemsActivated(const KFileItemList &items);
/**
* Is emitted if items have been added or deleted.
*/
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.
/** 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);
void activate();
void slotItemActivated(int index);
- void slotItemsActivated(const KItemSet& indexes);
+ 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);
void updatePlaceholderLabel();
+ void tryShowNameToolTip(QEvent* event);
+
private:
void updatePalette();
void showLoadingPlaceholder();
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,
QHBoxLayout* layout = new QHBoxLayout(this);
layout->addWidget(m_preview);
- layout->addSpacing(layout->margin());
+ layout->addSpacing(layout->contentsMargins().left());
layout->addLayout(textLayout);
}
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());
}
* - Add the following lines at the top of fileviewsvnplugin.cpp:
* <code>
* #include <KPluginFactory>
- * #include <KPluginLoader>
* K_PLUGIN_CLASS_WITH_JSON(FileViewSvnPlugin, "fileviewsvnplugin.json")
* </code>
*
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;
#include <KLocalizedString>
#include <KPluginFactory>
-#include <KPluginLoader>
#include <KPluginMetaData>
#include <QTimer>
{
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")) ) {
// 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()))) {
// 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();
/**
* Is invoked if the thread m_updateItemStatesThread has been finished
- * and applys the item states.
+ * and applies the item states.
*/
void slotThreadFinished();