diff options
Diffstat (limited to 'src')
47 files changed, 682 insertions, 159 deletions
diff --git a/src/dolphincontextmenu.cpp b/src/dolphincontextmenu.cpp index 41f03aa1a..028fd98bd 100644 --- a/src/dolphincontextmenu.cpp +++ b/src/dolphincontextmenu.cpp @@ -197,7 +197,6 @@ void DolphinContextMenu::addDirectoryItemContextMenu() // set up 'Create New' menu DolphinNewFileMenu* newFileMenu = new DolphinNewFileMenu(m_mainWindow->actionCollection(), m_mainWindow); const DolphinView* view = m_mainWindow->activeViewContainer()->view(); - newFileMenu->setViewShowsHiddenFiles(view->hiddenFilesShown()); newFileMenu->checkUpToDate(); newFileMenu->setPopupFiles(QList<QUrl>() << m_fileInfo.url()); newFileMenu->setEnabled(selectedItemsProps.supportsWriting()); @@ -315,7 +314,6 @@ void DolphinContextMenu::openViewportContextMenu() // Set up and insert 'Create New' menu KNewFileMenu* newFileMenu = m_mainWindow->newFileMenu(); - newFileMenu->setViewShowsHiddenFiles(view->hiddenFilesShown()); newFileMenu->checkUpToDate(); newFileMenu->setPopupFiles(QList<QUrl>() << m_baseUrl); addMenu(newFileMenu->menu()); @@ -477,7 +475,7 @@ KFileItem DolphinContextMenu::baseFileItem() void DolphinContextMenu::addOpenWithActions() { // insert 'Open With...' action or sub menu - m_fileItemActions->addOpenWithActionsTo(this, QStringLiteral("DesktopEntryName != '%1'").arg(qApp->desktopFileName())); + m_fileItemActions->insertOpenWithActionsTo(nullptr, this, QStringList{qApp->desktopFileName()}); } void DolphinContextMenu::addCustomActions() diff --git a/src/dolphinmainwindow.cpp b/src/dolphinmainwindow.cpp index fa105b15a..c03095c3c 100644 --- a/src/dolphinmainwindow.cpp +++ b/src/dolphinmainwindow.cpp @@ -133,7 +133,7 @@ DolphinMainWindow::DolphinMainWindow() : KIO::FileUndoManager* undoManager = KIO::FileUndoManager::self(); undoManager->setUiInterface(new UndoUiInterface()); - connect(undoManager, QOverload<bool>::of(&KIO::FileUndoManager::undoAvailable), + connect(undoManager, &KIO::FileUndoManager::undoAvailable, this, &DolphinMainWindow::slotUndoAvailable); connect(undoManager, &KIO::FileUndoManager::undoTextChanged, this, &DolphinMainWindow::slotUndoTextChanged); @@ -211,7 +211,7 @@ DolphinMainWindow::DolphinMainWindow() : setupWhatsThis(); - connect(KSycoca::self(), QOverload<>::of(&KSycoca::databaseChanged), this, &DolphinMainWindow::updateOpenPreferredSearchToolAction); + connect(KSycoca::self(), &KSycoca::databaseChanged, this, &DolphinMainWindow::updateOpenPreferredSearchToolAction); QTimer::singleShot(0, this, &DolphinMainWindow::updateOpenPreferredSearchToolAction); @@ -445,6 +445,16 @@ void DolphinMainWindow::openNewTab(const QUrl& url) m_tabWidget->openNewTab(url, QUrl()); } +void DolphinMainWindow::openNewTabAndActivate(const QUrl &url) +{ + m_tabWidget->openNewActivatedTab(url, QUrl()); +} + +void DolphinMainWindow::openNewWindow(const QUrl &url) +{ + Dolphin::openNewWindow({url}, this); +} + void DolphinMainWindow::slotSplitViewChanged() { m_tabWidget->currentTabPage()->setSplitViewEnabled(GeneralSettings::splitView(), WithAnimation); @@ -647,14 +657,12 @@ void DolphinMainWindow::readProperties(const KConfigGroup& group) void DolphinMainWindow::updateNewMenu() { - m_newFileMenu->setViewShowsHiddenFiles(activeViewContainer()->view()->hiddenFilesShown()); m_newFileMenu->checkUpToDate(); m_newFileMenu->setPopupFiles(QList<QUrl>() << activeViewContainer()->url()); } void DolphinMainWindow::createDirectory() { - m_newFileMenu->setViewShowsHiddenFiles(activeViewContainer()->view()->hiddenFilesShown()); m_newFileMenu->setPopupFiles(QList<QUrl>() << activeViewContainer()->url()); m_newFileMenu->createDirectory(); } @@ -1480,7 +1488,7 @@ void DolphinMainWindow::setupActions() QAction* showFilterBar = actionCollection()->addAction(QStringLiteral("show_filter_bar")); showFilterBar->setText(i18nc("@action:inmenu Tools", "Filter...")); - showFilterBar->setToolTip(i18nc("@info:tooltip", "Toggle Filter Bar")); + showFilterBar->setToolTip(i18nc("@info:tooltip", "Show Filter Bar")); showFilterBar->setWhatsThis(xi18nc("@info:whatsthis", "This opens the " "<emphasis>Filter Bar</emphasis> at the bottom of the window.<nl/> " "There you can enter a text to filter the files and folders currently displayed. " @@ -1861,8 +1869,10 @@ void DolphinMainWindow::setupDockWidgets() foldersPanel, &FoldersPanel::setUrl); connect(foldersPanel, &FoldersPanel::folderActivated, this, &DolphinMainWindow::changeUrl); - connect(foldersPanel, &FoldersPanel::folderMiddleClicked, + connect(foldersPanel, &FoldersPanel::folderInNewTab, this, &DolphinMainWindow::openNewTab); + connect(foldersPanel, &FoldersPanel::folderInNewActiveTab, + this, &DolphinMainWindow::openNewTabAndActivate); connect(foldersPanel, &FoldersPanel::errorMessage, this, &DolphinMainWindow::showErrorMessage); @@ -1944,8 +1954,10 @@ void DolphinMainWindow::setupDockWidgets() addDockWidget(Qt::LeftDockWidgetArea, placesDock); connect(m_placesPanel, &PlacesPanel::placeActivated, this, &DolphinMainWindow::slotPlaceActivated); - connect(m_placesPanel, &PlacesPanel::placeMiddleClicked, + connect(m_placesPanel, &PlacesPanel::placeActivatedInNewTab, this, &DolphinMainWindow::openNewTab); + connect(m_placesPanel, &PlacesPanel::placeActivatedInNewActiveTab, + this, &DolphinMainWindow::openNewTabAndActivate); connect(m_placesPanel, &PlacesPanel::errorMessage, this, &DolphinMainWindow::showErrorMessage); connect(this, &DolphinMainWindow::urlChanged, @@ -2136,6 +2148,10 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer* container) this, &DolphinMainWindow::updateSearchAction); connect(container, &DolphinViewContainer::captionChanged, this, &DolphinMainWindow::updateWindowTitle); + connect(container, &DolphinViewContainer::tabRequested, + this, &DolphinMainWindow::openNewTab); + connect(container, &DolphinViewContainer::activeTabRequested, + this, &DolphinMainWindow::openNewTabAndActivate); const QAction* toggleSearchAction = actionCollection()->action(QStringLiteral("toggle_search")); connect(toggleSearchAction, &QAction::triggered, container, &DolphinViewContainer::setSearchModeEnabled); @@ -2149,6 +2165,10 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer* container) this, &DolphinMainWindow::fileItemsChanged); connect(view, &DolphinView::tabRequested, this, &DolphinMainWindow::openNewTab); + connect(view, &DolphinView::activeTabRequested, + this, &DolphinMainWindow::openNewTabAndActivate); + connect(view, &DolphinView::windowRequested, + this, &DolphinMainWindow::openNewWindow); connect(view, &DolphinView::requestContextMenu, this, &DolphinMainWindow::openContextMenu); connect(view, &DolphinView::directoryLoadingStarted, @@ -2183,6 +2203,10 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer* container) this, &DolphinMainWindow::slotEditableStateChanged); connect(navigator, &KUrlNavigator::tabRequested, this, &DolphinMainWindow::openNewTab); + connect(navigator, &KUrlNavigator::activeTabRequested, + this, &DolphinMainWindow::openNewTabAndActivate); + connect(navigator, &KUrlNavigator::newWindowRequested, + this, &DolphinMainWindow::openNewWindow); } diff --git a/src/dolphinmainwindow.h b/src/dolphinmainwindow.h index 46515cc8b..761766df8 100644 --- a/src/dolphinmainwindow.h +++ b/src/dolphinmainwindow.h @@ -171,6 +171,16 @@ public Q_SLOTS: */ void openNewTab(const QUrl& url); + /** + * Opens a new tab showing the URL \a url and activate it. + */ + void openNewTabAndActivate(const QUrl &url); + + /** + * Opens a new window showing the URL \a url. + */ + void openNewWindow(const QUrl &url); + /** @see GeneralSettings::splitViewChanged() */ void slotSplitViewChanged(); diff --git a/src/dolphinnavigatorswidgetaction.h b/src/dolphinnavigatorswidgetaction.h index 3f50728e9..d33482201 100644 --- a/src/dolphinnavigatorswidgetaction.h +++ b/src/dolphinnavigatorswidgetaction.h @@ -57,7 +57,7 @@ public: * This method should preferably only be called when: * - Split view is activated in the active tab * OR - * - A switch to a tab that is already in split view mode is occuring + * - A switch to a tab that is already in split view mode is occurring */ void createSecondaryUrlNavigator(); @@ -92,7 +92,7 @@ protected: * this method always returns the same widget and reparents it. * You normally don't have to use this method directly because * QWidgetAction::requestWidget() is used to obtain the navigatorsWidget - * and to steal it from whereever it was prior. + * and to steal it from wherever it was prior. * @param parent the new parent of the navigatorsWidget. */ QWidget *createWidget(QWidget *parent) override; diff --git a/src/dolphinpart.cpp b/src/dolphinpart.cpp index 8d528f418..059508778 100644 --- a/src/dolphinpart.cpp +++ b/src/dolphinpart.cpp @@ -68,7 +68,7 @@ DolphinPart::DolphinPart(QWidget* parentWidget, QObject* parent, connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::errorMessage, this, &DolphinPart::slotErrorMessage); - connect(m_view, &DolphinView::directoryLoadingCompleted, this, QOverload<>::of(&KParts::ReadOnlyPart::completed)); + connect(m_view, &DolphinView::directoryLoadingCompleted, this, &KParts::ReadOnlyPart::completed); connect(m_view, &DolphinView::directoryLoadingCompleted, this, &DolphinPart::updatePasteAction); connect(m_view, &DolphinView::directoryLoadingProgress, this, &DolphinPart::updateProgress); connect(m_view, &DolphinView::errorMessage, this, &DolphinPart::slotErrorMessage); @@ -94,7 +94,7 @@ DolphinPart::DolphinPart(QWidget* parentWidget, QObject* parent, connect(m_view, &DolphinView::requestContextMenu, this, &DolphinPart::slotOpenContextMenu); connect(m_view, &DolphinView::selectionChanged, - m_extension, QOverload<const KFileItemList&>::of(&KParts::BrowserExtension::selectionInfo)); + m_extension, &KParts::BrowserExtension::selectionInfo); connect(m_view, &DolphinView::selectionChanged, this, &DolphinPart::slotSelectionChanged); connect(m_view, &DolphinView::requestItemInfo, @@ -146,8 +146,6 @@ DolphinPart::DolphinPart(QWidget* parentWidget, QObject* parent, // TODO there was a "always open a new window" (when clicking on a directory) setting in konqueror // (sort of spacial navigation) - - loadPlugins(this, this, componentName()); } DolphinPart::~DolphinPart() @@ -593,7 +591,6 @@ void DolphinPart::updateNewMenu() { // As requested by KNewFileMenu : m_newFileMenu->checkUpToDate(); - m_newFileMenu->setViewShowsHiddenFiles(m_view->hiddenFilesShown()); // And set the files that the menu apply on : m_newFileMenu->setPopupFiles(QList<QUrl>() << url()); } @@ -610,7 +607,6 @@ void DolphinPart::updateProgress(int percent) void DolphinPart::createDirectory() { - m_newFileMenu->setViewShowsHiddenFiles(m_view->hiddenFilesShown()); m_newFileMenu->setPopupFiles(QList<QUrl>() << url()); m_newFileMenu->createDirectory(); } diff --git a/src/dolphintabpage.h b/src/dolphintabpage.h index f1a784eb7..a8c1ba311 100644 --- a/src/dolphintabpage.h +++ b/src/dolphintabpage.h @@ -46,7 +46,7 @@ public: * * @param enabled If true, creates a secondary viewContainer in this tab. * Otherwise deletes it. - * @param animated Decides wether the effects of this method call should + * @param animated Decides whether the effects of this method call should * happen instantly or be transitioned to smoothly. * @param secondaryUrl If \p enabled is true, the new viewContainer will be opened at this * parameter. The default value will set the Url of the new viewContainer diff --git a/src/dolphinurlnavigatorscontroller.h b/src/dolphinurlnavigatorscontroller.h index 4f6802725..fec15f481 100644 --- a/src/dolphinurlnavigatorscontroller.h +++ b/src/dolphinurlnavigatorscontroller.h @@ -41,7 +41,7 @@ public Q_SLOTS: private: /** - * @return wether the places selector of DolphinUrlNavigators should be visible. + * @return whether the places selector of DolphinUrlNavigators should be visible. */ static bool placesSelectorVisible(); diff --git a/src/dolphinviewcontainer.cpp b/src/dolphinviewcontainer.cpp index 5520bf794..48e73ca89 100644 --- a/src/dolphinviewcontainer.cpp +++ b/src/dolphinviewcontainer.cpp @@ -32,6 +32,7 @@ #include <KUrlComboBox> #include <QDropEvent> +#include <QGuiApplication> #include <QLoggingCategory> #include <QMimeData> #include <QTimer> @@ -151,6 +152,10 @@ DolphinViewContainer::DolphinViewContainer(const QUrl& url, QWidget* parent) : this, &DolphinViewContainer::slotUrlIsFileError); connect(m_view, &DolphinView::activated, this, &DolphinViewContainer::activate); + connect(m_view, &DolphinView::hiddenFilesShownChanged, + this, &DolphinViewContainer::slotHiddenFilesShownChanged); + connect(m_view, &DolphinView::sortHiddenLastChanged, + this, &DolphinViewContainer::slotSortHiddenLastChanged); // Initialize status bar m_statusBar = new DolphinStatusBar(this); @@ -309,6 +314,8 @@ void DolphinViewContainer::connectUrlNavigator(DolphinUrlNavigator *urlNavigator Q_CHECK_PTR(m_view); urlNavigator->setLocationUrl(m_view->url()); + urlNavigator->setShowHiddenFolders(m_view->hiddenFilesShown()); + urlNavigator->setSortHiddenFoldersLast(m_view->sortHiddenLast()); if (m_urlNavigatorVisualState) { urlNavigator->setVisualState(*m_urlNavigatorVisualState.get()); m_urlNavigatorVisualState.reset(); @@ -636,7 +643,7 @@ void DolphinViewContainer::slotUrlIsFileError(const QUrl& url) } } -void DolphinViewContainer::slotItemActivated(const 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 @@ -645,13 +652,24 @@ void DolphinViewContainer::slotItemActivated(const KFileItem& item) const QUrl& url = DolphinView::openItemAsFolderUrl(item, GeneralSettings::browseThroughArchives()); if (!url.isEmpty()) { - setUrl(url); + const auto modifiers = QGuiApplication::keyboardModifiers(); + // keep in sync with KUrlNavigator::slotNavigatorButtonClicked + if (modifiers & Qt::ControlModifier && modifiers & Qt::ShiftModifier) { + Q_EMIT activeTabRequested(url); + } else if (modifiers & Qt::ControlModifier) { + Q_EMIT tabRequested(url); + } else if (modifiers & Qt::ShiftModifier) { + Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(url)}, this); + } else { + setUrl(url); + } return; } KIO::OpenUrlJob *job = new KIO::OpenUrlJob(item.targetUrl()); - job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this)); + job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoWarningHandlingEnabled, this)); job->setShowOpenOrExecuteDialog(true); + connect(job, &KIO::OpenUrlJob::finished, this, &DolphinViewContainer::slotOpenUrlFinished); job->start(); } @@ -660,7 +678,7 @@ void DolphinViewContainer::slotItemsActivated(const KFileItemList& items) Q_ASSERT(items.count() >= 2); KFileItemActions fileItemActions(this); - fileItemActions.runPreferredApplications(items, QString()); + fileItemActions.runPreferredApplications(items); } void DolphinViewContainer::showItemInfo(const KFileItem& item) @@ -809,6 +827,27 @@ void DolphinViewContainer::slotPlacesModelChanged() } } +void DolphinViewContainer::slotHiddenFilesShownChanged(bool showHiddenFiles) +{ + if (m_urlNavigatorConnected) { + m_urlNavigatorConnected->setShowHiddenFolders(showHiddenFiles); + } +} + +void DolphinViewContainer::slotSortHiddenLastChanged(bool hiddenLast) +{ + if (m_urlNavigatorConnected) { + m_urlNavigatorConnected->setSortHiddenFoldersLast(hiddenLast); + } +} + +void DolphinViewContainer::slotOpenUrlFinished(KJob *job) +{ + if (job->error() && job->error() != KIO::ERR_USER_CANCELED) { + showErrorMessage(job->errorString()); + } +} + bool DolphinViewContainer::isSearchUrl(const QUrl& url) const { return url.scheme().contains(QLatin1String("search")); diff --git a/src/dolphinviewcontainer.h b/src/dolphinviewcontainer.h index 304c9958d..f78f85e55 100644 --- a/src/dolphinviewcontainer.h +++ b/src/dolphinviewcontainer.h @@ -126,7 +126,7 @@ public: void connectUrlNavigator(DolphinUrlNavigator *urlNavigator); /** - * Disconnects the navigator that is currently controling the view. + * Disconnects the navigator that is currently controlling the view. * This method completely reverses connectUrlNavigator(). */ void disconnectUrlNavigator(); @@ -229,6 +229,16 @@ Q_SIGNALS: */ void captionChanged(); + /** + * Is emitted if a new tab should be opened in the background for the URL \a url. + */ + void tabRequested(const QUrl &url); + + /** + * Is emitted if a new tab should be opened for the URL \a url and set as active. + */ + void activeTabRequested(const QUrl &url); + private Q_SLOTS: /** * Updates the number of items (= number of files + number of @@ -281,7 +291,7 @@ private Q_SLOTS: * directory is opened in the view. If the item is a file, the file * gets started by the corresponding application. */ - void slotItemActivated(const KFileItem& item); + void slotItemActivated(const KFileItem &item); /** * Handles activation of multiple files. The files get started by @@ -361,6 +371,11 @@ private Q_SLOTS: */ void slotPlacesModelChanged(); + void slotHiddenFilesShownChanged(bool showHiddenFiles); + void slotSortHiddenLastChanged(bool hiddenLast); + + void slotOpenUrlFinished(KJob* job); + private: /** * @return True if the URL protocol is a search URL (e. g. baloosearch:// or filenamesearch://). diff --git a/src/kitemviews/kfileitemmodel.cpp b/src/kitemviews/kfileitemmodel.cpp index d12355100..07f8832ee 100644 --- a/src/kitemviews/kfileitemmodel.cpp +++ b/src/kitemviews/kfileitemmodel.cpp @@ -25,6 +25,7 @@ #include <QWidget> #include <QRecursiveMutex> #include <QIcon> +#include <algorithm> Q_GLOBAL_STATIC(QRecursiveMutex, s_collatorMutex) @@ -64,15 +65,15 @@ KFileItemModel::KFileItemModel(QObject* parent) : } connect(m_dirLister, &KCoreDirLister::started, this, &KFileItemModel::directoryLoadingStarted); - connect(m_dirLister, QOverload<>::of(&KCoreDirLister::canceled), this, &KFileItemModel::slotCanceled); + connect(m_dirLister, &KCoreDirLister::canceled, this, &KFileItemModel::slotCanceled); connect(m_dirLister, &KCoreDirLister::itemsAdded, this, &KFileItemModel::slotItemsAdded); connect(m_dirLister, &KCoreDirLister::itemsDeleted, this, &KFileItemModel::slotItemsDeleted); connect(m_dirLister, &KCoreDirLister::refreshItems, this, &KFileItemModel::slotRefreshItems); - connect(m_dirLister, QOverload<>::of(&KCoreDirLister::clear), this, &KFileItemModel::slotClear); + connect(m_dirLister, &KCoreDirLister::clear, this, &KFileItemModel::slotClear); connect(m_dirLister, &KCoreDirLister::infoMessage, this, &KFileItemModel::infoMessage); connect(m_dirLister, &KCoreDirLister::jobError, this, &KFileItemModel::slotListerError); connect(m_dirLister, &KCoreDirLister::percent, this, &KFileItemModel::directoryLoadingProgress); - connect(m_dirLister, QOverload<const QUrl&, const QUrl&>::of(&KCoreDirLister::redirection), this, &KFileItemModel::directoryRedirection); + connect(m_dirLister, &KCoreDirLister::redirection, this, &KFileItemModel::directoryRedirection); connect(m_dirLister, &KCoreDirLister::listingDirCompleted, this, &KFileItemModel::slotCompleted); // Apply default roles that should be determined @@ -149,6 +150,18 @@ QHash<QByteArray, QVariant> KFileItemModel::data(int index) const ItemData* data = m_itemData.at(index); if (data->values.isEmpty()) { data->values = retrieveData(data->item, data->parent); + } else if (data->values.count() <= 2 && data->values.value("isExpanded").toBool()) { + // Special case dealt by slotRefreshItems(), avoid losing the "isExpanded" and "expandedParentsCount" state when refreshing + // slotRefreshItems() makes sure folders keep the "isExpanded" and "expandedParentsCount" while clearing the remaining values + // so this special request of different behavior can be identified here. + bool hasExpandedParentsCount = false; + const int expandedParentsCount = data->values.value("expandedParentsCount").toInt(&hasExpandedParentsCount); + + data->values = retrieveData(data->item, data->parent); + data->values.insert("isExpanded", true); + if (hasExpandedParentsCount) { + data->values.insert("expandedParentsCount", expandedParentsCount); + } } return data->values; @@ -712,7 +725,7 @@ void KFileItemModel::applyFilters() ItemData *itemData = m_itemData.at(index); if (m_filter.matches(itemData->item) - || (itemShownBelow && itemShownBelow->parent == itemData && itemData->values.value("isExpanded").toBool())) { + || (itemShownBelow && itemShownBelow->parent == itemData)) { // We could've entered here for two reasons: // 1. This item passes the filter itself // 2. This is an expanded folder that doesn't pass the filter but sees a filter-passing child just below @@ -1010,12 +1023,7 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis { Q_ASSERT(!items.isEmpty()); - QUrl parentUrl; - if (m_expandedDirs.contains(directoryUrl)) { - parentUrl = m_expandedDirs.value(directoryUrl); - } else { - parentUrl = directoryUrl.adjusted(QUrl::StripTrailingSlash); - } + const QUrl parentUrl = m_expandedDirs.value(directoryUrl, directoryUrl.adjusted(QUrl::StripTrailingSlash)); if (m_requestRole[ExpandedParentsCountRole]) { // If the expanding of items is enabled, the call @@ -1049,16 +1057,28 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis if (!m_filter.hasSetFilters()) { m_pendingItemsToInsert.append(itemDataList); } else { + QSet<ItemData *> parentsToEnsureVisible; + // The name or type filter is active. Hide filtered items // before inserting them into the model and remember // the filtered items in m_filteredItems. for (ItemData* itemData : itemDataList) { if (m_filter.matches(itemData->item)) { m_pendingItemsToInsert.append(itemData); + if (itemData->parent) { + parentsToEnsureVisible.insert(itemData->parent); + } } else { m_filteredItems.insert(itemData->item, itemData); } } + + // Entire parental chains must be shown + for (ItemData *parent : parentsToEnsureVisible) { + for (; parent && m_filteredItems.remove(parent->item); parent = parent->parent) { + m_pendingItemsToInsert.append(parent); + } + } } if (!m_maximumUpdateIntervalTimer->isActive()) { @@ -1070,6 +1090,42 @@ void KFileItemModel::slotItemsAdded(const QUrl &directoryUrl, const KFileItemLis Q_EMIT fileItemsChanged({KFileItem(directoryUrl)}); } +int KFileItemModel::filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible) +{ + int filteredParentsCount = 0; + // The childless parents not yet removed will always be right above the start of a removed range. + // We iterate backwards to ensure the deepest folders are processed before their parents + for (int i = removedItemRanges.size() - 1; i >= 0; i--) { + KItemRange itemRange = removedItemRanges.at(i); + const ItemData *const firstInRange = m_itemData.at(itemRange.index); + ItemData *itemAbove = itemRange.index - 1 >= 0 ? m_itemData.at(itemRange.index - 1) : nullptr; + const ItemData *const itemBelow = itemRange.index + itemRange.count < m_itemData.count() ? m_itemData.at(itemRange.index + itemRange.count) : nullptr; + + if (itemAbove && firstInRange->parent == itemAbove && !m_filter.matches(itemAbove->item) && (!itemBelow || itemBelow->parent != itemAbove) + && !parentsToEnsureVisible.contains(itemAbove)) { + // The item above exists, is the parent, doesn't pass the filter, does not belong to parentsToEnsureVisible + // and this deleted range covers all of its descendents, so none will be left. + m_filteredItems.insert(itemAbove->item, itemAbove); + // This range's starting index will be extended to include the parent above: + --itemRange.index; + ++itemRange.count; + ++filteredParentsCount; + KItemRange previousRange = i > 0 ? removedItemRanges.at(i - 1) : KItemRange(); + // We must check if this caused the range to touch the previous range, if that's the case they shall be merged + if (i > 0 && previousRange.index + previousRange.count == itemRange.index) { + previousRange.count += itemRange.count; + removedItemRanges.replace(i - 1, previousRange); + removedItemRanges.removeAt(i); + } else { + removedItemRanges.replace(i, itemRange); + // We must revisit this range in the next iteration since its starting index changed + ++i; + } + } + } + return filteredParentsCount; +} + void KFileItemModel::slotItemsDeleted(const KFileItemList& items) { dispatchPendingItemsToInsert(); @@ -1119,9 +1175,17 @@ void KFileItemModel::slotItemsDeleted(const KFileItemList& items) indexesToRemove = indexesToRemoveWithChildren; } - const KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove); + KItemRangeList itemRanges = KItemRangeList::fromSortedContainer(indexesToRemove); removeFilteredChildren(itemRanges); - removeItems(itemRanges, DeleteItemData); + + // This call will update itemRanges to include the childless parents that have been filtered. + const int filteredParentsCount = filterChildlessParents(itemRanges); + + // If any childless parents were filtered, then itemRanges got updated and now contains items that were really deleted + // mixed with expanded folders that are just being filtered out. + // If that's the case, we pass 'DeleteItemDataIfUnfiltered' as a hint + // so removeItems() will check m_filteredItems to differentiate which is which. + removeItems(itemRanges, filteredParentsCount > 0 ? DeleteItemDataIfUnfiltered : DeleteItemData); Q_EMIT fileItemsChanged(dirsChanged); } @@ -1149,6 +1213,7 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& QList<ItemData*> newVisibleItems; QListIterator<QPair<KFileItem, KFileItem> > it(items); + while (it.hasNext()) { const QPair<KFileItem, KFileItem>& itemPair = it.next(); const KFileItem& oldItem = itemPair.first; @@ -1172,8 +1237,14 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& } m_items.remove(oldItem.url()); - if (newItemMatchesFilter) { - m_items.insert(newItem.url(), indexForItem); + // We must maintain m_items consistent with m_itemData for now, this very loop is using it. + // We leave it to be cleared by removeItems() later, when m_itemData actually gets updated. + m_items.insert(newItem.url(), indexForItem); + if (newItemMatchesFilter + || (itemData->values.value("isExpanded").toBool() + && (indexForItem + 1 < m_itemData.count() && m_itemData.at(indexForItem + 1)->parent == itemData))) { + // We are lenient with expanded folders that originally had visible children. + // If they become childless now they will be caught by filterChildlessParents() changedFiles.append(newItem); indexes.append(indexForItem); } else { @@ -1184,12 +1255,23 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& // Check if 'oldItem' is one of the filtered items. QHash<KFileItem, ItemData*>::iterator it = m_filteredItems.find(oldItem); if (it != m_filteredItems.end()) { - ItemData* itemData = it.value(); + ItemData *const itemData = it.value(); itemData->item = newItem; // The data stored in 'values' might have changed. Therefore, we clear // 'values' and re-populate it the next time it is requested via data(int). + // Before clearing, we must remember if it was expanded and the expanded parents count, + // otherwise these states would be lost. The data() method will deal with this special case. + const bool isExpanded = itemData->values.value("isExpanded").toBool(); + bool hasExpandedParentsCount = false; + const int expandedParentsCount = itemData->values.value("expandedParentsCount").toInt(&hasExpandedParentsCount); itemData->values.clear(); + if (isExpanded) { + itemData->values.insert("isExpanded", true); + if (hasExpandedParentsCount) { + itemData->values.insert("expandedParentsCount", expandedParentsCount); + } + } m_filteredItems.erase(it); if (newItemMatchesFilter) { @@ -1201,21 +1283,66 @@ void KFileItemModel::slotRefreshItems(const QList<QPair<KFileItem, KFileItem> >& } } - // Hide items, previously visible that should get hidden - const KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes); + std::sort(newFilteredIndexes.begin(), newFilteredIndexes.end()); + + // We must keep track of parents of new visible items since they must be shown no matter what + // They will be considered "immune" to filterChildlessParents() + QSet<ItemData *> parentsToEnsureVisible; + + for (ItemData *item : newVisibleItems) { + for (ItemData *parent = item->parent; parent && !parentsToEnsureVisible.contains(parent); parent = parent->parent) { + parentsToEnsureVisible.insert(parent); + } + } + for (ItemData *parent : parentsToEnsureVisible) { + // We make sure they are all unfiltered. + if (m_filteredItems.remove(parent->item)) { + // If it is being unfiltered now, we mark it to be inserted by appending it to newVisibleItems + newVisibleItems.append(parent); + // It could be in newFilteredIndexes, we must remove it if it's there: + const int parentIndex = index(parent->item); + if (parentIndex >= 0) { + QVector<int>::iterator it = std::lower_bound(newFilteredIndexes.begin(), newFilteredIndexes.end(), parentIndex); + if (it != newFilteredIndexes.end() && *it == parentIndex) { + newFilteredIndexes.erase(it); + } + } + } + } + + KItemRangeList removedRanges = KItemRangeList::fromSortedContainer(newFilteredIndexes); + + // This call will update itemRanges to include the childless parents that have been filtered. + filterChildlessParents(removedRanges, parentsToEnsureVisible); + removeItems(removedRanges, KeepItemData); // Show previously hidden items that should get visible insertItems(newVisibleItems); + // Final step: we will emit 'itemsChanged' and 'fileItemsChanged' signals and trigger the asynchronous re-sorting logic. + // If the changed items have been created recently, they might not be in m_items yet. // In that case, the list 'indexes' might be empty. if (indexes.isEmpty()) { return; } + if (newVisibleItems.count() > 0 || removedRanges.count() > 0) { + // The original indexes have changed and are now worthless since items were removed and/or inserted. + indexes.clear(); + // m_items is not yet rebuilt at this point, so we use our own means to resolve the new indexes. + const QSet<const KFileItem> changedFilesSet(changedFiles.cbegin(), changedFiles.cend()); + for (int i = 0; i < m_itemData.count(); i++) { + if (changedFilesSet.contains(m_itemData.at(i)->item)) { + indexes.append(i); + } + } + } else { + std::sort(indexes.begin(), indexes.end()); + } + // Extract the item-ranges out of the changed indexes - std::sort(indexes.begin(), indexes.end()); const KItemRangeList itemRangeList = KItemRangeList::fromSortedContainer(indexes); emitItemsChangedAndTriggerResorting(itemRangeList, changedRoles); @@ -1380,7 +1507,7 @@ void KFileItemModel::removeItems(const KItemRangeList& itemRanges, RemoveItemsBe removedItemsCount += range.count; for (int index = range.index; index < range.index + range.count; ++index) { - if (behavior == DeleteItemData) { + if (behavior == DeleteItemData || (behavior == DeleteItemDataIfUnfiltered && !m_filteredItems.contains(m_itemData.at(index)->item))) { delete m_itemData.at(index); } @@ -1424,8 +1551,9 @@ QList<KFileItemModel::ItemData*> KFileItemModel::createItemDataList(const QUrl& determineMimeTypes(items, 200); } + // We search for the parent in m_itemData and then in m_filteredItems if necessary const int parentIndex = index(parentUrl); - ItemData* parentItem = parentIndex < 0 ? nullptr : m_itemData.at(parentIndex); + ItemData *parentItem = parentIndex < 0 ? m_filteredItems.value(KFileItem(parentUrl), nullptr) : m_itemData.at(parentIndex); QList<ItemData*> itemDataList; itemDataList.reserve(items.count()); diff --git a/src/kitemviews/kfileitemmodel.h b/src/kitemviews/kfileitemmodel.h index 161f6a0e2..471cfc2d2 100644 --- a/src/kitemviews/kfileitemmodel.h +++ b/src/kitemviews/kfileitemmodel.h @@ -309,7 +309,8 @@ private: enum RemoveItemsBehavior { KeepItemData, - DeleteItemData + DeleteItemData, + DeleteItemDataIfUnfiltered }; void insertItems(QList<ItemData*>& items); @@ -469,6 +470,15 @@ private: */ bool isConsistent() const; + /** + * Filters out the expanded folders that don't pass the filter themselves and don't have any filter-passing children. + * Will update the removedItemRanges arguments to include the parents that have been filtered. + * @returns the number of parents that have been filtered. + * @param removedItemRanges The ranges of items being deleted/filtered, will get updated + * @param parentsToEnsureVisible Parents that must be visible no matter what due to being ancestors of newly visible items + */ + int filterChildlessParents(KItemRangeList &removedItemRanges, const QSet<ItemData *> &parentsToEnsureVisible = QSet<ItemData *>()); + private: KDirLister *m_dirLister = nullptr; diff --git a/src/kitemviews/kfileitemmodelrolesupdater.cpp b/src/kitemviews/kfileitemmodelrolesupdater.cpp index 978f5df6e..49657a9b1 100644 --- a/src/kitemviews/kfileitemmodelrolesupdater.cpp +++ b/src/kitemviews/kfileitemmodelrolesupdater.cpp @@ -18,7 +18,7 @@ #include <KIconLoader> #include <KJobWidgets> #include <KOverlayIconPlugin> -#include <KPluginLoader> +#include <KPluginMetaData> #include <KSharedConfig> #ifdef HAVE_BALOO @@ -30,6 +30,7 @@ #include <QApplication> #include <QIcon> #include <QPainter> +#include <QPluginLoader> #include <QElapsedTimer> #include <QTimer> @@ -120,15 +121,16 @@ KFileItemModelRolesUpdater::KFileItemModelRolesUpdater(KFileItemModel* model, QO connect(m_directoryContentsCounter, &KDirectoryContentsCounter::result, this, &KFileItemModelRolesUpdater::slotDirectoryContentsCountReceived); - const auto plugins = KPluginLoader::instantiatePlugins(QStringLiteral("kf5/overlayicon"), nullptr, qApp); - for (QObject *it : plugins) { - auto plugin = qobject_cast<KOverlayIconPlugin*>(it); + const auto plugins = KPluginMetaData::findPlugins(QStringLiteral("kf5/overlayicon")); + for (const KPluginMetaData &data : plugins) { + auto instance = QPluginLoader(data.fileName()).instance(); + auto plugin = qobject_cast<KOverlayIconPlugin *>(instance); if (plugin) { m_overlayIconsPlugin.append(plugin); connect(plugin, &KOverlayIconPlugin::overlaysChanged, this, &KFileItemModelRolesUpdater::slotOverlaysChanged); } else { // not our/valid plugin, so delete the created object - it->deleteLater(); + delete instance; } } } @@ -1405,10 +1407,19 @@ QList<int> KFileItemModelRolesUpdater::indexesToResolve() const (2 * m_maximumVisibleItems))); // Add visible items. + // Resolve files first, their previews are quicker. + QList<int> visibleDirs; for (int i = m_firstVisibleIndex; i <= m_lastVisibleIndex; ++i) { - result.append(i); + const KFileItem item = m_model->fileItem(i); + if (item.isDir()) { + visibleDirs.append(i); + } else { + result.append(i); + } } + result.append(visibleDirs); + // We need a reasonable upper limit for number of items to resolve after // and before the visible range. m_maximumVisibleItems can be quite large // when using Compact View. diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index 8687872ee..d0bcd6ceb 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -1583,14 +1583,10 @@ bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifi Q_EMIT itemExpansionToggleClicked(index); 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; diff --git a/src/kitemviews/kitemlistcontroller.h b/src/kitemviews/kitemlistcontroller.h index 24339134e..f4092576a 100644 --- a/src/kitemviews/kitemlistcontroller.h +++ b/src/kitemviews/kitemlistcontroller.h @@ -137,7 +137,7 @@ Q_SIGNALS: * 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); diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index 5c8c712e8..9492f6a44 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -2237,11 +2237,11 @@ QHash<QByteArray, qreal> KItemListView::preferredColumnWidths(const KItemRangeLi const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin); for (const QByteArray& visibleRole : qAsConst(m_visibleRoles)) { const QString headerText = m_model->roleDescription(visibleRole); - const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2; + const qreal headerWidth = fontMetrics.horizontalAdvance(headerText) + gripMargin + headerMargin * 2; widths.insert(visibleRole, headerWidth); } - // Calculate the preferred column withs for each item and ignore values + // Calculate the preferred column widths for each item and ignore values // smaller than the width for showing the headline unclipped. const KItemListWidgetCreatorBase* creator = widgetCreator(); int calculatedItemCount = 0; diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index 760e0a415..e28487b27 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -597,7 +597,7 @@ private: /** * Resizes the column-widths of m_headerWidget based on the preferred widths - * and the vailable view-size. + * and the available view-size. */ void applyAutomaticColumnWidths(); @@ -873,7 +873,7 @@ qreal KItemListWidgetCreator<T>::preferredRoleColumnWidth(const QByteArray& role * @brief Base class for creating KItemListGroupHeaders. * * It is recommended that applications simply use the KItemListGroupHeaderCreator-template class. - * For a custom implementation the methods create() and recyle() must be reimplemented. + * For a custom implementation the methods create() and recycle() must be reimplemented. * The intention of the group-header creator is to prevent repetitive and expensive instantiations and * deletions of KItemListGroupHeaders by recycling existing header instances. */ diff --git a/src/kitemviews/private/kitemlistheaderwidget.cpp b/src/kitemviews/private/kitemlistheaderwidget.cpp index e5cbc602f..9a7e850a9 100644 --- a/src/kitemviews/private/kitemlistheaderwidget.cpp +++ b/src/kitemviews/private/kitemlistheaderwidget.cpp @@ -251,7 +251,7 @@ void KItemListHeaderWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* event) case NoRoleOperation: if ((event->pos() - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) { // A role gets dragged by the user. Create a pixmap of the role that will get - // synchronized on each furter mouse-move-event with the mouse-position. + // synchronized on each further mouse-move-event with the mouse-position. m_roleOperation = MoveRoleOperation; const int roleIndex = roleIndexAt(m_pressedMousePos); m_movingRole.index = roleIndex; diff --git a/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp b/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp index 57a954adf..0e6280ede 100644 --- a/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp +++ b/src/kitemviews/private/kitemlistkeyboardsearchmanager.cpp @@ -55,7 +55,7 @@ void KItemListKeyboardSearchManager::addKeys(const QString& keys) const bool searchFromNextItem = (!m_isSearchRestarted && newSearch) || sameKey; // to remember not to searchFromNextItem if selection was deselected - // loosing keyboard search context basically + // losing keyboard search context basically m_isSearchRestarted = false; Q_EMIT changeCurrentItem(sameKey ? firstKey : m_searchedString, searchFromNextItem); diff --git a/src/org.kde.dolphin.desktop b/src/org.kde.dolphin.desktop index 1d2f43f64..ad5ee3789 100755 --- a/src/org.kde.dolphin.desktop +++ b/src/org.kde.dolphin.desktop @@ -115,6 +115,24 @@ GenericName[zh_TW]=檔案管理員 Terminal=false MimeType=inode/directory; InitialPreference=10 +Keywords=files;file management;file browsing;samba;network shares;Explorer;Finder; +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[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[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[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 diff --git a/src/panels/folders/folderspanel.cpp b/src/panels/folders/folderspanel.cpp index 19a05d2b6..d3d8b81f1 100644 --- a/src/panels/folders/folderspanel.cpp +++ b/src/panels/folders/folderspanel.cpp @@ -189,7 +189,19 @@ void FoldersPanel::slotItemActivated(int index) { const KFileItem item = m_model->fileItem(index); if (!item.isNull()) { - Q_EMIT folderActivated(item.url()); + const auto modifiers = QGuiApplication::keyboardModifiers(); + // keep in sync with KUrlNavigator::slotNavigatorButtonClicked + if (modifiers & Qt::ControlModifier && modifiers & Qt::ShiftModifier) { + Q_EMIT folderInNewActiveTab(item.url()); + } else if (modifiers & Qt::ControlModifier) { + Q_EMIT folderInNewTab(item.url()); + } else if (modifiers & Qt::ShiftModifier) { + // The shift modifier is not considered because it is used to expand the tree view without actually + // opening the folder + return; + } else { + Q_EMIT folderActivated(item.url()); + } } } @@ -197,7 +209,13 @@ void FoldersPanel::slotItemMiddleClicked(int index) { const KFileItem item = m_model->fileItem(index); if (!item.isNull()) { - Q_EMIT folderMiddleClicked(item.url()); + const auto modifiers = QGuiApplication::keyboardModifiers(); + // keep in sync with KUrlNavigator::slotNavigatorButtonClicked + if (modifiers & Qt::ShiftModifier) { + Q_EMIT folderInNewActiveTab(item.url()); + } else { + Q_EMIT folderInNewTab(item.url()); + } } } diff --git a/src/panels/folders/folderspanel.h b/src/panels/folders/folderspanel.h index 26c8e4cb9..3ce7870ff 100644 --- a/src/panels/folders/folderspanel.h +++ b/src/panels/folders/folderspanel.h @@ -42,7 +42,8 @@ public: Q_SIGNALS: void folderActivated(const QUrl& url); - void folderMiddleClicked(const QUrl& url); + void folderInNewTab(const QUrl &url); + void folderInNewActiveTab(const QUrl &url); void errorMessage(const QString& error); protected: diff --git a/src/panels/information/informationpanelcontent.cpp b/src/panels/information/informationpanelcontent.cpp index 98c012243..bb5f793cd 100644 --- a/src/panels/information/informationpanelcontent.cpp +++ b/src/panels/information/informationpanelcontent.cpp @@ -23,9 +23,6 @@ #include <Baloo/FileMetaDataWidget> -#include <panels/places/placesitem.h> -#include <panels/places/placesitemmodel.h> - #include <Phonon/BackendCapabilities> #include <Phonon/MediaObject> @@ -60,7 +57,6 @@ InformationPanelContent::InformationPanelContent(QWidget* parent) : m_nameLabel(nullptr), m_metaDataWidget(nullptr), m_metaDataArea(nullptr), - m_placesItemModel(nullptr), m_isVideo(false) { parent->installEventFilter(this); @@ -151,8 +147,6 @@ InformationPanelContent::InformationPanelContent(QWidget* parent) : layout->addWidget(m_configureButtons); grabGesture(Qt::TapAndHoldGesture); - - m_placesItemModel = new PlacesItemModel(this); } InformationPanelContent::~InformationPanelContent() diff --git a/src/panels/information/informationpanelcontent.h b/src/panels/information/informationpanelcontent.h index 78fcf3cd0..38383bd41 100644 --- a/src/panels/information/informationpanelcontent.h +++ b/src/panels/information/informationpanelcontent.h @@ -17,7 +17,6 @@ class KFileItemList; class PhononWidget; class PixmapViewer; -class PlacesItemModel; class QPixmap; class QDialogButtonBox; class QString; @@ -151,7 +150,6 @@ private: QLabel* m_configureLabel; QDialogButtonBox* m_configureButtons; - PlacesItemModel* m_placesItemModel; bool m_isVideo; }; diff --git a/src/panels/places/placesitem.cpp b/src/panels/places/placesitem.cpp index 9cac01f91..18f3f006e 100644 --- a/src/panels/places/placesitem.cpp +++ b/src/panels/places/placesitem.cpp @@ -264,7 +264,7 @@ QString PlacesItem::generateNewId() // " (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()) + + return QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()) + '/' + QString::number(count++) + " (V2)"; } diff --git a/src/panels/places/placespanel.cpp b/src/panels/places/placespanel.cpp index 83e014a82..b9fc4a897 100644 --- a/src/panels/places/placespanel.cpp +++ b/src/panels/places/placespanel.cpp @@ -36,6 +36,7 @@ #include <KPropertiesDialog> #include <QActionGroup> +#include <QApplication> #include <QGraphicsSceneDragDropEvent> #include <QIcon> #include <QMenu> @@ -44,16 +45,16 @@ #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() + Panel(parent), + m_controller(nullptr), + m_model(nullptr), + m_view(nullptr), + m_storageSetupFailedUrl(), + m_triggerStorageSetupModifier(), + m_itemDropEventIndex(-1), + m_itemDropEventMimeData(nullptr), + m_itemDropEvent(nullptr), + m_tooltipTimer() { m_tooltipTimer.setInterval(500); m_tooltipTimer.setSingleShot(true); @@ -163,12 +164,28 @@ bool PlacesPanel::eventFilter(QObject * /* obj */, QEvent *event) void PlacesPanel::slotItemActivated(int index) { - triggerItem(index, Qt::LeftButton); + const auto modifiers = QGuiApplication::keyboardModifiers(); + // keep in sync with KUrlNavigator::slotNavigatorButtonClicked + if (modifiers & Qt::ControlModifier && modifiers & Qt::ShiftModifier) { + triggerItem(index, TriggerItemModifier::ToNewActiveTab); + } else if (modifiers & Qt::ControlModifier) { + triggerItem(index, TriggerItemModifier::ToNewTab); + } else if (modifiers & Qt::ShiftModifier) { + triggerItem(index, TriggerItemModifier::ToNewWindow); + } else { + triggerItem(index, TriggerItemModifier::None); + } } void PlacesPanel::slotItemMiddleClicked(int index) { - triggerItem(index, Qt::MiddleButton); + const auto modifiers = QGuiApplication::keyboardModifiers(); + // keep in sync with KUrlNavigator::slotNavigatorButtonClicked + if (modifiers & Qt::ShiftModifier) { + triggerItem(index, TriggerItemModifier::ToNewActiveTab); + } else { + triggerItem(index, TriggerItemModifier::ToNewTab); + } } void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos) @@ -287,9 +304,7 @@ void PlacesPanel::slotItemContextMenuRequested(int index, const QPointF& pos) } 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); + triggerItem(index, TriggerItemModifier::ToNewTab); } else if (action == mountAction) { m_model->requestStorageSetup(index); } else if (action == teardownAction) { @@ -480,14 +495,14 @@ void PlacesPanel::slotStorageSetupDone(int index, bool success) disconnect(m_model, &PlacesItemModel::storageSetupDone, this, &PlacesPanel::slotStorageSetupDone); - if (m_triggerStorageSetupButton == Qt::NoButton) { + if (m_triggerStorageSetupModifier == TriggerItemModifier::None) { return; } if (success) { Q_ASSERT(!m_model->storageSetupNeeded(index)); - triggerItem(index, m_triggerStorageSetupButton); - m_triggerStorageSetupButton = Qt::NoButton; + triggerItem(index, m_triggerStorageSetupModifier); + m_triggerStorageSetupModifier = TriggerItemModifier::None; } else { setUrl(m_storageSetupFailedUrl); m_storageSetupFailedUrl = QUrl(); @@ -553,7 +568,7 @@ void PlacesPanel::selectItem() } } -void PlacesPanel::triggerItem(int index, Qt::MouseButton button) +void PlacesPanel::triggerItem(int index, TriggerItemModifier modifier) { const PlacesItem* item = m_model->placesItem(index); if (!item) { @@ -561,7 +576,7 @@ void PlacesPanel::triggerItem(int index, Qt::MouseButton button) } if (m_model->storageSetupNeeded(index)) { - m_triggerStorageSetupButton = button; + m_triggerStorageSetupModifier = modifier; m_storageSetupFailedUrl = url(); connect(m_model, &PlacesItemModel::storageSetupDone, @@ -569,14 +584,23 @@ void PlacesPanel::triggerItem(int index, Qt::MouseButton button) m_model->requestStorageSetup(index); } else { - m_triggerStorageSetupButton = Qt::NoButton; + m_triggerStorageSetupModifier = TriggerItemModifier::None; 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)); + switch (modifier) { + case TriggerItemModifier::ToNewTab: + Q_EMIT placeActivatedInNewTab(KFilePlacesModel::convertedUrl(url)); + break; + case TriggerItemModifier::ToNewActiveTab: + Q_EMIT placeActivatedInNewActiveTab(KFilePlacesModel::convertedUrl(url)); + break; + case TriggerItemModifier::ToNewWindow: + Dolphin::openNewWindow({KFilePlacesModel::convertedUrl(url)}, this); + break; + case TriggerItemModifier::None: + Q_EMIT placeActivated(KFilePlacesModel::convertedUrl(url)); + break; } } } diff --git a/src/panels/places/placespanel.h b/src/panels/places/placespanel.h index 39f8da365..ce28c8c08 100644 --- a/src/panels/places/placespanel.h +++ b/src/panels/places/placespanel.h @@ -35,7 +35,8 @@ public: Q_SIGNALS: void placeActivated(const QUrl& url); - void placeMiddleClicked(const QUrl& url); + void placeActivatedInNewTab(const QUrl &url); + void placeActivatedInNewActiveTab(const QUrl &url); void errorMessage(const QString& error); void storageTearDownRequested(const QString& mountPath); void storageTearDownExternallyRequested(const QString& mountPath); @@ -64,6 +65,9 @@ private Q_SLOTS: void slotShowTooltip(); private: + enum class TriggerItemModifier { None, ToNewTab, ToNewActiveTab, ToNewWindow }; + +private: void addEntry(); void editEntry(int index); @@ -73,7 +77,7 @@ private: */ void selectItem(); - void triggerItem(int index, Qt::MouseButton button); + void triggerItem(int index, TriggerItemModifier modifier); QAction* buildGroupContextMenu(QMenu* menu, int index); @@ -83,7 +87,7 @@ private: PlacesView* m_view; QUrl m_storageSetupFailedUrl; - Qt::MouseButton m_triggerStorageSetupButton; + TriggerItemModifier m_triggerStorageSetupModifier; int m_itemDropEventIndex; QMimeData* m_itemDropEventMimeData; diff --git a/src/panels/terminal/terminalpanel.cpp b/src/panels/terminal/terminalpanel.cpp index 3af2cdcad..9d30dcd62 100644 --- a/src/panels/terminal/terminalpanel.cpp +++ b/src/panels/terminal/terminalpanel.cpp @@ -15,7 +15,6 @@ #include <KMountPoint> #include <KParts/ReadOnlyPart> #include <KPluginFactory> -#include <KPluginLoader> #include <KProtocolInfo> #include <KShell> #include <kde_terminal_interface.h> @@ -129,8 +128,7 @@ void TerminalPanel::showEvent(QShowEvent* event) if (!m_terminal) { m_clearTerminal = true; - KPluginLoader loader(QStringLiteral("konsolepart")); - KPluginFactory* factory = loader.factory(); + KPluginFactory *factory = KPluginFactory::loadFactory(KPluginMetaData(QStringLiteral("konsolepart"))).plugin; m_konsolePart = factory ? (factory->create<KParts::ReadOnlyPart>(this)) : nullptr; if (m_konsolePart) { connect(m_konsolePart, &KParts::ReadOnlyPart::destroyed, this, &TerminalPanel::terminalExited); diff --git a/src/search/dolphinfacetswidget.cpp b/src/search/dolphinfacetswidget.cpp index db53d595f..cc125a2d9 100644 --- a/src/search/dolphinfacetswidget.cpp +++ b/src/search/dolphinfacetswidget.cpp @@ -235,7 +235,7 @@ void DolphinFacetsWidget::initComboBox(QComboBox* combo) combo->setFrame(false); combo->setMinimumHeight(parentWidget()->height()); combo->setCurrentIndex(0); - connect(combo, QOverload<int>::of(&QComboBox::activated), this, &DolphinFacetsWidget::facetChanged); + connect(combo, &QComboBox::activated, this, &DolphinFacetsWidget::facetChanged); } void DolphinFacetsWidget::updateTagsSelector() diff --git a/src/settings/contextmenu/contextmenusettingspage.cpp b/src/settings/contextmenu/contextmenusettingspage.cpp index 8ebac2e12..cfbfefe52 100644 --- a/src/settings/contextmenu/contextmenusettingspage.cpp +++ b/src/settings/contextmenu/contextmenusettingspage.cpp @@ -275,7 +275,7 @@ void ContextMenuSettingsPage::loadServices() 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 QList<KServiceAction> serviceActions = KDesktopFileActions::userDefinedServices(KService(file), true); const KDesktopFile desktopFile(file); const QString subMenuName = desktopFile.desktopGroup().readEntry("X-KDE-Submenu"); @@ -307,7 +307,7 @@ void ContextMenuSettingsPage::loadServices() #endif // Load JSON-based plugins that implement the KFileItemActionPlugin interface - const auto jsonPlugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kfileitemaction")); + const auto jsonPlugins = KPluginMetaData::findPlugins(QStringLiteral("kf5/kfileitemaction")); for (const auto &jsonMetadata : jsonPlugins) { const QString desktopEntryName = jsonMetadata.pluginId(); @@ -328,7 +328,7 @@ void ContextMenuSettingsPage::loadVersionControlSystems() // Create a checkbox for each available version control plugin QSet<QString> loadedPlugins; - const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("dolphin/vcs")); + const QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("dolphin/vcs")); for (const auto &plugin : plugins) { const QString pluginName = plugin.name(); addRow(QStringLiteral("code-class"), diff --git a/src/settings/dolphinsettingsdialog.cpp b/src/settings/dolphinsettingsdialog.cpp index 3b7d4b267..d699ef894 100644 --- a/src/settings/dolphinsettingsdialog.cpp +++ b/src/settings/dolphinsettingsdialog.cpp @@ -90,7 +90,7 @@ DolphinSettingsDialog::DolphinSettingsDialog(const QUrl& url, QWidget* parent, K }); KPageWidgetItem* contextMenuSettingsFrame = addPage(contextMenuSettingsPage, i18nc("@title:group", "Context Menu")); - contextMenuSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("application-menu"))); + contextMenuSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-menu-edit"))); connect(contextMenuSettingsPage, &ContextMenuSettingsPage::changed, this, &DolphinSettingsDialog::enableApply); // Trash diff --git a/src/settings/general/configurepreviewplugindialog.cpp b/src/settings/general/configurepreviewplugindialog.cpp index 26b7deb88..b60ba5a0c 100644 --- a/src/settings/general/configurepreviewplugindialog.cpp +++ b/src/settings/general/configurepreviewplugindialog.cpp @@ -11,7 +11,7 @@ #include <KIO/ThumbCreator> #include <KJobWidgets> #include <KLocalizedString> -#include <KPluginLoader> +#include <QPluginLoader> #include <QDialogButtonBox> #include <QPushButton> @@ -25,7 +25,7 @@ ConfigurePreviewPluginDialog::ConfigurePreviewPluginDialog(const QString& plugin QDialog(parent) { QSharedPointer<ThumbCreator> previewPlugin; - const QString pluginPath = KPluginLoader::findPlugin(desktopEntryName); + const QString pluginPath = QPluginLoader(desktopEntryName).fileName(); if (!pluginPath.isEmpty()) { newCreator create = (newCreator)QLibrary::resolve(pluginPath, "new_creator"); if (create) { diff --git a/src/settings/general/previewssettingspage.cpp b/src/settings/general/previewssettingspage.cpp index 564715ae3..5d176f465 100644 --- a/src/settings/general/previewssettingspage.cpp +++ b/src/settings/general/previewssettingspage.cpp @@ -94,8 +94,8 @@ PreviewsSettingsPage::PreviewsSettingsPage(QWidget* parent) : loadSettings(); connect(m_listView, &QListView::clicked, this, &PreviewsSettingsPage::changed); - connect(m_localFileSizeBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &PreviewsSettingsPage::changed); - connect(m_remoteFileSizeBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &PreviewsSettingsPage::changed); + connect(m_localFileSizeBox, &QSpinBox::valueChanged, this, &PreviewsSettingsPage::changed); + connect(m_remoteFileSizeBox, &QSpinBox::valueChanged, this, &PreviewsSettingsPage::changed); } PreviewsSettingsPage::~PreviewsSettingsPage() diff --git a/src/settings/trash/trashsettingspage.cpp b/src/settings/trash/trashsettingspage.cpp index df627fa1c..048ee0e9a 100644 --- a/src/settings/trash/trashsettingspage.cpp +++ b/src/settings/trash/trashsettingspage.cpp @@ -15,12 +15,12 @@ TrashSettingsPage::TrashSettingsPage(QWidget* parent) : { QFormLayout* topLayout = new QFormLayout(this); - m_proxy = new KCModuleProxy(QStringLiteral("kcmtrash")); + m_proxy = new KCModuleProxy(KPluginMetaData(QStringLiteral("kcm_trash"))); topLayout->addRow(m_proxy); loadSettings(); - connect(m_proxy, QOverload<bool>::of(&KCModuleProxy::changed), this, &TrashSettingsPage::changed); + connect(m_proxy, &KCModuleProxy::changed, this, &TrashSettingsPage::changed); } TrashSettingsPage::~TrashSettingsPage() diff --git a/src/settings/viewmodes/dolphinfontrequester.cpp b/src/settings/viewmodes/dolphinfontrequester.cpp index cb66870af..a4663e94a 100644 --- a/src/settings/viewmodes/dolphinfontrequester.cpp +++ b/src/settings/viewmodes/dolphinfontrequester.cpp @@ -27,7 +27,7 @@ DolphinFontRequester::DolphinFontRequester(QWidget* parent) : m_modeCombo = new QComboBox(this); m_modeCombo->addItem(i18nc("@item:inlistbox Font", "System Font")); m_modeCombo->addItem(i18nc("@item:inlistbox Font", "Custom Font")); - connect(m_modeCombo, QOverload<int>::of(&QComboBox::activated), + connect(m_modeCombo, &QComboBox::activated, this, &DolphinFontRequester::changeMode); m_chooseFontButton = new QPushButton(i18nc("@action:button Choose font", "Choose..."), this); diff --git a/src/settings/viewmodes/viewsettingstab.cpp b/src/settings/viewmodes/viewsettingstab.cpp index 7ea8d5809..1e109aab0 100644 --- a/src/settings/viewmodes/viewsettingstab.cpp +++ b/src/settings/viewmodes/viewsettingstab.cpp @@ -112,7 +112,7 @@ ViewSettingsTab::ViewSettingsTab(Mode mode, QWidget* parent) : sortingModeGroup->addButton(m_sizeOfContents); m_recursiveDirectorySizeLimit = new QSpinBox(); - connect(m_recursiveDirectorySizeLimit, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int value) { + connect(m_recursiveDirectorySizeLimit, &QSpinBox::valueChanged, this, [this](int value) { m_recursiveDirectorySizeLimit->setSuffix(i18np(" level deep", " levels deep", value)); }); m_recursiveDirectorySizeLimit->setRange(1, 20); @@ -155,16 +155,16 @@ ViewSettingsTab::ViewSettingsTab(Mode mode, QWidget* parent) : switch (m_mode) { case IconsMode: - connect(m_widthBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ViewSettingsTab::changed); - connect(m_maxLinesBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ViewSettingsTab::changed); + connect(m_widthBox, &QComboBox::currentIndexChanged, this, &ViewSettingsTab::changed); + connect(m_maxLinesBox, &QComboBox::currentIndexChanged, this, &ViewSettingsTab::changed); break; case CompactMode: - connect(m_widthBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ViewSettingsTab::changed); + connect(m_widthBox, &QComboBox::currentIndexChanged, this, &ViewSettingsTab::changed); break; case DetailsMode: connect(m_expandableFolders, &QCheckBox::toggled, this, &ViewSettingsTab::changed); #ifndef Q_OS_WIN - connect(m_recursiveDirectorySizeLimit, QOverload<int>::of(&QSpinBox::valueChanged), this, &ViewSettingsTab::changed); + connect(m_recursiveDirectorySizeLimit, &QSpinBox::valueChanged, this, &ViewSettingsTab::changed); connect(m_numberOfItems, &QRadioButton::toggled, this, &ViewSettingsTab::changed); connect(m_sizeOfContents, &QRadioButton::toggled, this, [=]() { m_recursiveDirectorySizeLimit->setEnabled(m_sizeOfContents->isChecked()); diff --git a/src/settings/viewpropertiesdialog.cpp b/src/settings/viewpropertiesdialog.cpp index 6659d79b6..318c2e1cf 100644 --- a/src/settings/viewpropertiesdialog.cpp +++ b/src/settings/viewpropertiesdialog.cpp @@ -150,11 +150,11 @@ ViewPropertiesDialog::ViewPropertiesDialog(DolphinView* dolphinView) : layout->addRow(QString(), m_showHiddenFiles); layout->addRow(QString(), m_sortHiddenLast); - connect(m_viewMode, QOverload<int>::of(&QComboBox::currentIndexChanged), + connect(m_viewMode, &QComboBox::currentIndexChanged, this, &ViewPropertiesDialog::slotViewModeChanged); - connect(m_sorting, QOverload<int>::of(&QComboBox::currentIndexChanged), + connect(m_sorting, &QComboBox::currentIndexChanged, this, &ViewPropertiesDialog::slotSortingChanged); - connect(m_sortOrder, QOverload<int>::of(&QComboBox::currentIndexChanged), + connect(m_sortOrder, &QComboBox::currentIndexChanged, this, &ViewPropertiesDialog::slotSortOrderChanged); connect(m_sortFoldersFirst, &QCheckBox::clicked, this, &ViewPropertiesDialog::slotSortFoldersFirstChanged); diff --git a/src/tests/kfileitemmodeltest.cpp b/src/tests/kfileitemmodeltest.cpp index 7a22a1a7f..679b8ab3a 100644 --- a/src/tests/kfileitemmodeltest.cpp +++ b/src/tests/kfileitemmodeltest.cpp @@ -73,6 +73,10 @@ private Q_SLOTS: void testNameFilter(); void testEmptyPath(); void testRefreshExpandedItem(); + void testAddItemToFilteredExpandedFolder(); + void testDeleteItemsWithExpandedFolderWithFilter(); + void testRefreshItemsWithFilter(); + void testRefreshExpandedFolderWithFilter(); void testRemoveHiddenItems(); void collapseParentOfHiddenItems(); void removeParentOfHiddenItems(); @@ -516,7 +520,7 @@ void KFileItemModelTest::testExpandItems() // Test expanding subfolders in a folder with the items "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1". // Besides testing the basic item expansion functionality, the test makes sure that // KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& b) - // yields the correct result for "a/a/1" and "a/a-1/", whis is non-trivial because they share the + // yields the correct result for "a/a/1" and "a/a-1/", which is non-trivial because they share the // first three characters. QSet<QByteArray> originalModelRoles = m_model->roles(); QSet<QByteArray> modelRoles = originalModelRoles; @@ -1144,6 +1148,219 @@ void KFileItemModelTest::testRefreshExpandedItem() } /** + * Verifies that adding an item to an expanded folder that's filtered makes the parental chain visible. + */ +void KFileItemModelTest::testAddItemToFilteredExpandedFolder() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy fileItemsChangedSpy(m_model, &KFileItemModel::fileItemsChanged); + + QSet<QByteArray> modelRoles = m_model->roles(); + modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + m_testDir->createFile("a/b/file"); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(m_model->count(), 1); // "a + + // Expand "a/". + m_model->setExpanded(0, true); + QVERIFY(itemsInsertedSpy.wait()); + + // Expand "a/b/". + m_model->setExpanded(1, true); + QVERIFY(itemsInsertedSpy.wait()); + + QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/file" + + const QUrl urlB = m_model->fileItem(1).url(); + + // Set a filter that matches ".txt" extension + m_model->setNameFilter("*.txt"); + QCOMPARE(m_model->count(), 0); // Everything got hidden since we don't have a .txt file yet + + m_model->slotItemsAdded(urlB, KFileItemList() << KFileItem(QUrl("a/b/newItem.txt"))); + m_model->slotCompleted(); + + // Entire parental chain should now be shown + QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/newItem.txt" + QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "newItem.txt"); + + // Items should be indented in hierarchy + QCOMPARE(m_model->expandedParentsCount(0), 0); + QCOMPARE(m_model->expandedParentsCount(1), 1); + QCOMPARE(m_model->expandedParentsCount(2), 2); +} + +/** + * Verifies that deleting the last filter-passing child from expanded folders + * makes the parental chain hidden. + */ +void KFileItemModelTest::testDeleteItemsWithExpandedFolderWithFilter() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + + QSet<QByteArray> modelRoles = m_model->roles(); + modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + m_testDir->createFile("a/b/file"); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(m_model->count(), 1); // "a + + // Expand "a/". + m_model->setExpanded(0, true); + QVERIFY(itemsInsertedSpy.wait()); + + // Expand "a/b/". + m_model->setExpanded(1, true); + QVERIFY(itemsInsertedSpy.wait()); + + QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/file" + + // Set a filter that matches "file" extension + m_model->setNameFilter("file"); + QCOMPARE(m_model->count(), 3); // Everything is still shown + + // Delete "file" + QCOMPARE(itemsRemovedSpy.count(), 0); + m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(2)); + QCOMPARE(itemsRemovedSpy.count(), 1); + + // Entire parental chain should now be filtered + QCOMPARE(m_model->count(), 0); + QCOMPARE(m_model->m_filteredItems.size(), 2); +} + +/** + * Verifies that the fileItemsChanged signal is raised with the correct index after renaming files with filter set. + * The rename operation will cause one item to be filtered out and another item to be reordered. + */ +void KFileItemModelTest::testRefreshItemsWithFilter() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + QSignalSpy itemsChangedSpy(m_model, &KFileItemModel::itemsChanged); + QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved); + + // Creates three .txt files + m_testDir->createFiles({"b.txt", "c.txt", "d.txt"}); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + + QCOMPARE(m_model->count(), 3); // "b.txt", "c.txt", "d.txt" + + // Set a filter that matches ".txt" extension + m_model->setNameFilter("*.txt"); + QCOMPARE(m_model->count(), 3); // Still all items are shown + QCOMPARE(itemsInModel(), QStringList() << "b.txt" << "c.txt" << "d.txt"); + + // Objects used to rename + const KFileItem fileItemC_txt = m_model->fileItem(1); + KFileItem fileItemC_cfg = fileItemC_txt; + fileItemC_cfg.setUrl(QUrl("c.cfg")); + + const KFileItem fileItemD_txt = m_model->fileItem(2); + KFileItem fileItemA_txt = fileItemD_txt; + fileItemA_txt.setUrl(QUrl("a.txt")); + + // Rename "c.txt" to "c.cfg"; and rename "d.txt" to "a.txt" + QCOMPARE(itemsRemovedSpy.count(), 0); + QCOMPARE(itemsChangedSpy.count(), 0); + m_model->slotRefreshItems({qMakePair(fileItemC_txt, fileItemC_cfg), qMakePair(fileItemD_txt, fileItemA_txt)}); + QCOMPARE(itemsRemovedSpy.count(), 1); + QCOMPARE(itemsChangedSpy.count(), 1); + QCOMPARE(m_model->count(), 2); // Only "a.txt" and "b.txt". "c.cfg" got filtered out + + QList<QVariant> arguments = itemsChangedSpy.takeLast(); + KItemRangeList itemRangeList = arguments.at(0).value<KItemRangeList>(); + + // We started with the order "b.txt", "c.txt", "d.txt" + // "d.txt" started with index "2" + // "c.txt" got renamed and got filtered out + // "d.txt" index shifted from index "2" to "1" + // So we expect index "1" in this argument, meaning "d.txt" was renamed + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 1)); + + // Re-sorting is done asynchronously: + QCOMPARE(itemsInModel(), QStringList() << "b.txt" << "a.txt"); // Files should still be in the incorrect order + QVERIFY(itemsMovedSpy.wait()); + QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt"); // Files were re-sorted and should now be in the correct order +} + + +/** + * Verifies that parental chains are hidden and shown as needed while their children get filtered/unfiltered due to renaming. + * Also verifies that the "isExpanded" and "expandedParentsCount" values are kept for expanded folders that get refreshed. + */ +void KFileItemModelTest::testRefreshExpandedFolderWithFilter() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + + QSet<QByteArray> modelRoles = m_model->roles(); + modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + m_testDir->createFile("a/b/someFolder/someFile"); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + + QCOMPARE(m_model->count(), 1); // Only "a/" + + // Expand "a/". + m_model->setExpanded(0, true); + QVERIFY(itemsInsertedSpy.wait()); + + // Expand "a/b/". + m_model->setExpanded(1, true); + QVERIFY(itemsInsertedSpy.wait()); + + // Expand "a/b/someFolder/". + m_model->setExpanded(2, true); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(m_model->count(), 4); // 4 items: "a/", "a/b/", "a/b/someFolder", "a/b/someFolder/someFile" + + // Set a filter that matches the expanded folder "someFolder" + m_model->setNameFilter("someFolder"); + QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/someFolder" + + // Objects used to rename + const KFileItem fileItemA = m_model->fileItem(0); + KFileItem fileItemARenamed = fileItemA; + fileItemARenamed.setUrl(QUrl("a_renamed")); + + const KFileItem fileItemSomeFolder = m_model->fileItem(2); + KFileItem fileItemRenamedFolder = fileItemSomeFolder; + fileItemRenamedFolder.setUrl(QUrl("/a_renamed/b/renamedFolder")); + + // Rename "a" to "a_renamed" + // This way we test if the algorithm is sane as to NOT hide "a_renamed" since it will have visible children + m_model->slotRefreshItems({qMakePair(fileItemA, fileItemARenamed)}); + QCOMPARE(m_model->count(), 3); // Entire parental chain must still be shown + QCOMPARE(itemsInModel(), QStringList() << "a_renamed" << "b" << "someFolder"); + + // Rename "a_renamed" back to "a"; and "someFolder" to "renamedFolder" + m_model->slotRefreshItems({qMakePair(fileItemARenamed, fileItemA), qMakePair(fileItemSomeFolder, fileItemRenamedFolder)}); + QCOMPARE(m_model->count(), 0); // Entire parental chain became hidden + + // Rename "renamedFolder" back to "someFolder". Filter is passing again + m_model->slotRefreshItems({qMakePair(fileItemRenamedFolder, fileItemSomeFolder)}); + QCOMPARE(m_model->count(), 3); // Entire parental chain is shown again + QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "someFolder"); + + // slotRefreshItems() should preserve "isExpanded" and "expandedParentsCount" values explicitly in this case + QCOMPARE(m_model->m_itemData.at(2)->values.value("isExpanded").toBool(), true); + QCOMPARE(m_model->m_itemData.at(2)->values.value("expandedParentsCount"), 2); +} + +/** * Verify that removing hidden files and folders from the model does not * result in a crash, see https://bugs.kde.org/show_bug.cgi?id=314046 */ @@ -1298,8 +1515,7 @@ void KFileItemModelTest::removeParentOfHiddenItems() // Simulate the deletion of the directory "a/b/". m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(1)); QCOMPARE(itemsRemovedSpy.count(), 2); - QCOMPARE(m_model->count(), 1); - QCOMPARE(itemsInModel(), QStringList() << "a"); + QCOMPARE(m_model->count(), 0); // "a" will be filtered out since it doesn't pass the filter and doesn't have visible children // Remove the filter -> only the file "a/1" should appear. m_model->setNameFilter(QString()); diff --git a/src/tests/testdir.cpp b/src/tests/testdir.cpp index 51dbdbc58..c05752171 100644 --- a/src/tests/testdir.cpp +++ b/src/tests/testdir.cpp @@ -31,7 +31,7 @@ static void setTimeStamp(const QString& path, const QDateTime& mtime) { #ifdef Q_OS_UNIX struct utimbuf utbuf; - utbuf.actime = mtime.toTime_t(); + utbuf.actime = mtime.toSecsSinceEpoch(); utbuf.modtime = utbuf.actime; utime(QFile::encodeName(path), &utbuf); #elif defined(Q_OS_WIN) diff --git a/src/trash/dolphintrash.cpp b/src/trash/dolphintrash.cpp index df8834556..1446ab388 100644 --- a/src/trash/dolphintrash.cpp +++ b/src/trash/dolphintrash.cpp @@ -28,7 +28,7 @@ Trash::Trash() bool isTrashEmpty = m_trashDirLister->items().isEmpty(); Q_EMIT emptinessChanged(isTrashEmpty); }; - connect(m_trashDirLister, QOverload<>::of(&KCoreDirLister::completed), this, trashDirContentChanged); + connect(m_trashDirLister, &KCoreDirLister::completed, this, trashDirContentChanged); connect(m_trashDirLister, &KDirLister::itemsDeleted, this, trashDirContentChanged); m_trashDirLister->openUrl(QUrl(QStringLiteral("trash:/"))); } diff --git a/src/views/dolphinremoteencoding.cpp b/src/views/dolphinremoteencoding.cpp index 41b3b6890..c7c8b09d1 100644 --- a/src/views/dolphinremoteencoding.cpp +++ b/src/views/dolphinremoteencoding.cpp @@ -90,7 +90,7 @@ void DolphinRemoteEncoding::fillMenu() QMenu* menu = m_menu->menu(); menu->clear(); - + menu->addAction(i18n("Default"), this, SLOT(slotDefault()), 0)->setCheckable(true); for (int i = 0; i < m_encodingDescriptions.size();i++) { QAction* action = new QAction(m_encodingDescriptions.at(i), this); action->setCheckable(true); @@ -100,7 +100,6 @@ void DolphinRemoteEncoding::fillMenu() menu->addSeparator(); menu->addAction(i18n("Reload"), this, SLOT(slotReload()), 0); - menu->addAction(i18n("Default"), this, SLOT(slotDefault()), 0)->setCheckable(true); m_idDefault = m_encodingDescriptions.size() + 2; connect(menu, &QMenu::triggered, this, &DolphinRemoteEncoding::slotItemSelected); diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index 9a063d857..bb537b982 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -974,12 +974,14 @@ void DolphinView::slotItemActivated(int index) } } -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); @@ -995,8 +997,15 @@ void DolphinView::slotItemsActivated(const KItemSet& indexes) KFileItem item = m_model->fileItem(index); const QUrl& url = openItemAsFolderUrl(item); - if (!url.isEmpty()) { // Open folders in new tabs - Q_EMIT tabRequested(url); + if (!url.isEmpty()) { + // Open folders in new tabs or in new windows depending on the modifier + // The ctrl+shift behavior is ignored because we are handling multiple items + // keep in sync with KUrlNavigator::slotNavigatorButtonClicked + if (modifiers & Qt::ShiftModifier && !(modifiers & Qt::ControlModifier)) { + Q_EMIT windowRequested(url); + } else { + Q_EMIT tabRequested(url); + } } else { items.append(item); } @@ -1013,10 +1022,21 @@ void DolphinView::slotItemMiddleClicked(int index) { const KFileItem& item = m_model->fileItem(index); const QUrl& url = openItemAsFolderUrl(item); + const auto modifiers = QGuiApplication::keyboardModifiers(); if (!url.isEmpty()) { - Q_EMIT tabRequested(url); + // keep in sync with KUrlNavigator::slotNavigatorButtonClicked + if (modifiers & Qt::ShiftModifier) { + Q_EMIT activeTabRequested(url); + } else { + Q_EMIT tabRequested(url); + } } else if (isTabsForFilesEnabled()) { - Q_EMIT tabRequested(item.url()); + // keep in sync with KUrlNavigator::slotNavigatorButtonClicked + if (modifiers & Qt::ShiftModifier) { + Q_EMIT activeTabRequested(item.url()); + } else { + Q_EMIT tabRequested(item.url()); + } } } diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index 75c9dd985..0f288f942 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -425,13 +425,13 @@ Q_SIGNALS: /** * 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. @@ -444,6 +444,16 @@ Q_SIGNALS: void tabRequested(const QUrl& url); /** + * Is emitted if a new tab should be opened for the URL \a url and set as active. + */ + void activeTabRequested(const QUrl &url); + + /** + * Is emitted if a new window should be opened for the URL \a url. + */ + void windowRequested(const QUrl &url); + + /** * Is emitted if the view mode (IconsView, DetailsView, * PreviewsView) has been changed. */ @@ -458,7 +468,7 @@ Q_SIGNALS: /** Is emitted if the 'grouped sorting' property has been changed. */ void groupedSortingChanged(bool groupedSorting); - /** Is emmited in reaction to a requestStatusBarText() call. + /** Is emitted in reaction to a requestStatusBarText() call. * @see requestStatusBarText() */ void statusBarTextChanged(QString statusBarText); @@ -619,7 +629,7 @@ private Q_SLOTS: 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); diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index 47247ec35..2e524f8f2 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -213,7 +213,7 @@ void DolphinViewActionHandler::createActions() viewModeActions->addAction(compactAction); viewModeActions->addAction(detailsAction); viewModeActions->setToolBarMode(KSelectAction::MenuMode); - connect(viewModeActions, QOverload<QAction*>::of(&KSelectAction::triggered), this, &DolphinViewActionHandler::slotViewModeActionTriggered); + connect(viewModeActions, &KSelectAction::triggered, this, &DolphinViewActionHandler::slotViewModeActionTriggered); QAction* zoomInAction = KStandardAction::zoomIn(this, &DolphinViewActionHandler::zoomIn, diff --git a/src/views/tooltips/dolphinfilemetadatawidget.cpp b/src/views/tooltips/dolphinfilemetadatawidget.cpp index b147135bf..e914593fb 100644 --- a/src/views/tooltips/dolphinfilemetadatawidget.cpp +++ b/src/views/tooltips/dolphinfilemetadatawidget.cpp @@ -66,7 +66,7 @@ DolphinFileMetaDataWidget::DolphinFileMetaDataWidget(QWidget* parent) : QHBoxLayout* layout = new QHBoxLayout(this); layout->addWidget(m_preview); - layout->addSpacing(layout->margin()); + layout->addSpacing(layout->contentsMargins().left()); layout->addLayout(textLayout); } diff --git a/src/views/versioncontrol/kversioncontrolplugin.h b/src/views/versioncontrol/kversioncontrolplugin.h index c908be247..d3a39fbd6 100644 --- a/src/views/versioncontrol/kversioncontrolplugin.h +++ b/src/views/versioncontrol/kversioncontrolplugin.h @@ -45,7 +45,6 @@ class KFileItem; * - Add the following lines at the top of fileviewsvnplugin.cpp: * <code> * #include <KPluginFactory> - * #include <KPluginLoader> * K_PLUGIN_CLASS_WITH_JSON(FileViewSvnPlugin, "fileviewsvnplugin.json") * </code> * @@ -143,8 +142,8 @@ public: virtual QString fileName() const = 0; /** - * Returns the path of the local repository root for the versionned directory - * Returns an emtpy QString when directory is not part of a working copy + * Returns the path of the local repository root for the versioned directory + * Returns an empty QString when directory is not part of a working copy */ virtual QString localRepositoryRoot(const QString& directory) const; diff --git a/src/views/versioncontrol/versioncontrolobserver.cpp b/src/views/versioncontrol/versioncontrolobserver.cpp index 6766aa479..a773aef6b 100644 --- a/src/views/versioncontrol/versioncontrolobserver.cpp +++ b/src/views/versioncontrol/versioncontrolobserver.cpp @@ -14,7 +14,6 @@ #include <KLocalizedString> #include <KPluginFactory> -#include <KPluginLoader> #include <KPluginMetaData> #include <QTimer> @@ -136,7 +135,7 @@ void VersionControlObserver::slotItemsChanged(const KItemRangeList& itemRanges, { Q_UNUSED(itemRanges) - // Because "version" role is emitted by VCS plugin (ourselfs) we don't need to + // Because "version" role is emitted by VCS plugin (ourselves) we don't need to // analyze it and update directory item states information. So lets check if // there is only "version". if ( !(roles.count() == 1 && roles.contains("version")) ) { @@ -164,7 +163,7 @@ void VersionControlObserver::verifyDirectory() // by an immediate verification. m_dirVerificationTimer->setInterval(500); } else { - // View was versionned but should not be anymore + // View was versioned but should not be anymore updateItemStates(); } } else if ((m_plugin = searchPlugin(rootItem.url()))) { @@ -280,15 +279,13 @@ void VersionControlObserver::initPlugins() // all fileview version control plugins and remember them in 'plugins'. const QStringList enabledPlugins = VersionControlSettings::enabledPlugins(); - const QVector<KPluginMetaData> plugins = KPluginLoader::findPlugins(QStringLiteral("dolphin/vcs")); + const QVector<KPluginMetaData> plugins = KPluginMetaData::findPlugins(QStringLiteral("dolphin/vcs")); QSet<QString> loadedPlugins; for (const auto &p : plugins) { if (enabledPlugins.contains(p.name())) { - KPluginLoader loader(p.fileName()); - KPluginFactory *factory = loader.factory(); - KVersionControlPlugin *plugin = factory->create<KVersionControlPlugin>(); + auto plugin = KPluginFactory::instantiatePlugin<KVersionControlPlugin>(p).plugin; if (plugin) { m_plugins.append(plugin); loadedPlugins += p.name(); diff --git a/src/views/versioncontrol/versioncontrolobserver.h b/src/views/versioncontrol/versioncontrolobserver.h index 6e3977fb2..064a3088f 100644 --- a/src/views/versioncontrol/versioncontrolobserver.h +++ b/src/views/versioncontrol/versioncontrolobserver.h @@ -95,7 +95,7 @@ private Q_SLOTS: /** * Is invoked if the thread m_updateItemStatesThread has been finished - * and applys the item states. + * and applies the item states. */ void slotThreadFinished(); |
