X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/2744fb4a0b70cdff98c4f7e857247828cffe791e..b68abb7d2718709c2a3ed1e92145ed4247b7333b:/src/views/dolphinview.cpp diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index 17465eaa9..f0f67c9d0 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -1,119 +1,97 @@ -/*************************************************************************** - * Copyright (C) 2006-2009 by Peter Penz * - * Copyright (C) 2006 by Gregor KaliÅ¡nik * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * - ***************************************************************************/ +/* + * SPDX-FileCopyrightText: 2006-2009 Peter Penz + * SPDX-FileCopyrightText: 2006 Gregor KaliÅ¡nik + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "dolphinview.h" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dolphinnewfilemenuobserver.h" #include "dolphin_detailsmodesettings.h" #include "dolphin_generalsettings.h" #include "dolphinitemlistview.h" +#include "dolphinnewfilemenuobserver.h" #include "draganddrophelper.h" -#include "renamedialog.h" +#include "kitemviews/kfileitemlistview.h" +#include "kitemviews/kfileitemmodel.h" +#include "kitemviews/kitemlistcontainer.h" +#include "kitemviews/kitemlistcontroller.h" +#include "kitemviews/kitemlistheader.h" +#include "kitemviews/kitemlistselectionmanager.h" #include "versioncontrol/versioncontrolobserver.h" -#include "viewmodecontroller.h" #include "viewproperties.h" #include "views/tooltips/tooltipmanager.h" #include "zoomlevelinfo.h" #ifdef HAVE_BALOO - #include +#include #endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace { - const int MaxModeEnum = DolphinView::CompactView; -}; +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -DolphinView::DolphinView(const KUrl& url, QWidget* parent) : +DolphinView::DolphinView(const QUrl& url, QWidget* parent) : QWidget(parent), m_active(true), m_tabsForFiles(false), m_assureVisibleCurrentIndex(false), m_isFolderWritable(true), m_dragging(false), + m_loading(false), m_url(url), m_viewPropertiesContext(), m_mode(DolphinView::IconsView), m_visibleRoles(), - m_topLayout(0), - m_model(0), - m_view(0), - m_container(0), - m_toolTipManager(0), - m_selectionChangedTimer(0), + m_topLayout(nullptr), + m_model(nullptr), + m_view(nullptr), + m_container(nullptr), + m_toolTipManager(nullptr), + m_selectionChangedTimer(nullptr), m_currentItemUrl(), m_scrollToCurrentItem(false), m_restoredContentsPosition(), m_selectedUrls(), m_clearSelectionBeforeSelectingNewItems(false), m_markFirstNewlySelectedItemAsCurrent(false), - m_versionControlObserver(0) + m_versionControlObserver(nullptr), + m_twoClicksRenamingTimer(nullptr), + m_placeholderLabel(nullptr) { m_topLayout = new QVBoxLayout(this); m_topLayout->setSpacing(0); - m_topLayout->setMargin(0); + m_topLayout->setContentsMargins(0, 0, 0, 0); // When a new item has been created by the "Create New..." menu, the item should // get selected and it must be assured that the item will get visible. As the @@ -130,7 +108,7 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) : m_model = new KFileItemModel(this); m_view = new DolphinItemListView(); m_view->setEnabledSelectionToggles(GeneralSettings::showSelectionToggle()); - m_view->setVisibleRoles(QList() << "text"); + m_view->setVisibleRoles({"text"}); applyModeToView(); KItemListController* controller = new KItemListController(m_model, m_view, this); @@ -144,8 +122,30 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) : m_container = new KItemListContainer(controller, this); m_container->installEventFilter(this); setFocusProxy(m_container); - connect(m_container->horizontalScrollBar(), &QScrollBar::valueChanged, this, &DolphinView::hideToolTip); - connect(m_container->verticalScrollBar(), &QScrollBar::valueChanged, this, &DolphinView::hideToolTip); + connect(m_container->horizontalScrollBar(), &QScrollBar::valueChanged, this, [=] { hideToolTip(); }); + connect(m_container->verticalScrollBar(), &QScrollBar::valueChanged, this, [=] { hideToolTip(); }); + + // Show some placeholder text for empty folders + // This is made using a heavily-modified QLabel rather than a KTitleWidget + // because KTitleWidget can't be told to turn off mouse-selectable text + m_placeholderLabel = new QLabel(this); + QFont placeholderLabelFont; + // To match the size of a level 2 Heading/KTitleWidget + placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3)); + m_placeholderLabel->setFont(placeholderLabelFont); + m_placeholderLabel->setTextInteractionFlags(Qt::NoTextInteraction); + m_placeholderLabel->setWordWrap(true); + m_placeholderLabel->setAlignment(Qt::AlignCenter); + // Match opacity of QML placeholder label component + auto *effect = new QGraphicsOpacityEffect(m_placeholderLabel); + effect->setOpacity(0.5); + m_placeholderLabel->setGraphicsEffect(effect); + // Set initial text and visibility + updatePlaceholderLabel(); + + auto *centeringLayout = new QVBoxLayout(m_container); + centeringLayout->addWidget(m_placeholderLabel); + centeringLayout->setAlignment(m_placeholderLabel, Qt::AlignCenter); controller->setSelectionBehavior(KItemListController::MultiSelection); connect(controller, &KItemListController::itemActivated, this, &DolphinView::slotItemActivated); @@ -160,10 +160,14 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) : connect(controller, &KItemListController::itemDropEvent, this, &DolphinView::slotItemDropEvent); connect(controller, &KItemListController::escapePressed, this, &DolphinView::stopLoading); connect(controller, &KItemListController::modelChanged, this, &DolphinView::slotModelChanged); + connect(controller, &KItemListController::selectedItemTextPressed, this, &DolphinView::slotSelectedItemTextPressed); + connect(controller, &KItemListController::increaseZoom, this, &DolphinView::slotIncreaseZoom); + connect(controller, &KItemListController::decreaseZoom, this, &DolphinView::slotDecreaseZoom); + connect(controller, &KItemListController::swipeUp, this, &DolphinView::slotSwipeUp); connect(m_model, &KFileItemModel::directoryLoadingStarted, this, &DolphinView::slotDirectoryLoadingStarted); connect(m_model, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); - connect(m_model, &KFileItemModel::directoryLoadingCanceled, this, &DolphinView::directoryLoadingCanceled); + connect(m_model, &KFileItemModel::directoryLoadingCanceled, this, &DolphinView::slotDirectoryLoadingCanceled); connect(m_model, &KFileItemModel::directoryLoadingProgress, this, &DolphinView::directoryLoadingProgress); connect(m_model, &KFileItemModel::directorySortingProgress, this, &DolphinView::directorySortingProgress); connect(m_model, &KFileItemModel::itemsChanged, @@ -175,6 +179,9 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) : connect(m_model, &KFileItemModel::directoryRedirection, this, &DolphinView::slotDirectoryRedirection); connect(m_model, &KFileItemModel::urlIsFileError, this, &DolphinView::urlIsFileError); + connect(this, &DolphinView::itemCountChanged, + this, &DolphinView::updatePlaceholderLabel); + m_view->installEventFilter(this); connect(m_view, &DolphinItemListView::sortOrderChanged, this, &DolphinView::slotSortOrderChangedByHeader); @@ -184,21 +191,29 @@ DolphinView::DolphinView(const KUrl& url, QWidget* parent) : this, &DolphinView::slotVisibleRolesChangedByHeader); connect(m_view, &DolphinItemListView::roleEditingCanceled, this, &DolphinView::slotRoleEditingCanceled); - connect(m_view->header(), &KItemListHeader::columnWidthChanged, - this, &DolphinView::slotHeaderColumnWidthChanged); + connect(m_view->header(), &KItemListHeader::columnWidthChangeFinished, + this, &DolphinView::slotHeaderColumnWidthChangeFinished); KItemListSelectionManager* selectionManager = controller->selectionManager(); connect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &DolphinView::slotSelectionChanged); +#ifdef HAVE_BALOO m_toolTipManager = new ToolTipManager(this); + connect(m_toolTipManager, &ToolTipManager::urlActivated, this, &DolphinView::urlActivated); +#endif m_versionControlObserver = new VersionControlObserver(this); + m_versionControlObserver->setView(this); m_versionControlObserver->setModel(m_model); connect(m_versionControlObserver, &VersionControlObserver::infoMessage, this, &DolphinView::infoMessage); connect(m_versionControlObserver, &VersionControlObserver::errorMessage, this, &DolphinView::errorMessage); connect(m_versionControlObserver, &VersionControlObserver::operationCompletedMessage, this, &DolphinView::operationCompletedMessage); + m_twoClicksRenamingTimer = new QTimer(this); + m_twoClicksRenamingTimer->setSingleShot(true); + connect(m_twoClicksRenamingTimer, &QTimer::timeout, this, &DolphinView::slotTwoClicksRenamingTimerTimeout); + applyViewProperties(); m_topLayout->addWidget(m_container); @@ -209,7 +224,7 @@ DolphinView::~DolphinView() { } -KUrl DolphinView::url() const +QUrl DolphinView::url() const { return m_url; } @@ -222,24 +237,12 @@ void DolphinView::setActive(bool active) m_active = active; - QColor color = KColorScheme(QPalette::Active, KColorScheme::View).background().color(); - if (!active) { - color.setAlpha(150); - } - - QWidget* viewport = m_container->viewport(); - if (viewport) { - QPalette palette; - palette.setColor(viewport->backgroundRole(), color); - viewport->setPalette(palette); - } - - update(); + updatePalette(); if (active) { m_container->setFocus(); - emit activated(); - emit writeStateChanged(m_isFolderWritable); + Q_EMIT activated(); + Q_EMIT writeStateChanged(m_isFolderWritable); } } @@ -278,11 +281,11 @@ void DolphinView::setPreviewsShown(bool show) const int oldZoomLevel = m_view->zoomLevel(); m_view->setPreviewsShown(show); - emit previewsShownChanged(show); + Q_EMIT previewsShownChanged(show); const int newZoomLevel = m_view->zoomLevel(); if (newZoomLevel != oldZoomLevel) { - emit zoomLevelChanged(newZoomLevel, oldZoomLevel); + Q_EMIT zoomLevelChanged(newZoomLevel, oldZoomLevel); } } @@ -299,13 +302,13 @@ void DolphinView::setHiddenFilesShown(bool show) const KFileItemList itemList = selectedItems(); m_selectedUrls.clear(); - m_selectedUrls = KUrl::List(itemList.urlList()); + m_selectedUrls = itemList.urlList(); ViewProperties props(viewPropertiesUrl()); props.setHiddenFilesShown(show); m_model->setShowHiddenFiles(show); - emit hiddenFilesShownChanged(show); + Q_EMIT hiddenFilesShownChanged(show); } bool DolphinView::hiddenFilesShown() const @@ -325,7 +328,7 @@ void DolphinView::setGroupedSorting(bool grouped) m_container->controller()->model()->setGroupedSorting(grouped); - emit groupedSortingChanged(grouped); + Q_EMIT groupedSortingChanged(grouped); } bool DolphinView::groupedSorting() const @@ -356,7 +359,9 @@ KFileItemList DolphinView::selectedItems() const const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); KFileItemList selectedItems; - foreach (int index, selectionManager->selectedItems()) { + const auto items = selectionManager->selectedItems(); + selectedItems.reserve(items.count()); + for (int index : items) { selectedItems.append(m_model->fileItem(index)); } return selectedItems; @@ -368,18 +373,18 @@ int DolphinView::selectedItemsCount() const return selectionManager->selectedItems().count(); } -void DolphinView::markUrlsAsSelected(const QList& urls) +void DolphinView::markUrlsAsSelected(const QList& urls) { m_selectedUrls = urls; } -void DolphinView::markUrlAsCurrent(const KUrl& url) +void DolphinView::markUrlAsCurrent(const QUrl &url) { m_currentItemUrl = url; m_scrollToCurrentItem = true; } -void DolphinView::selectItems(const QRegExp& pattern, bool enabled) +void DolphinView::selectItems(const QRegularExpression ®exp, bool enabled) { const KItemListSelectionManager::SelectionMode mode = enabled ? KItemListSelectionManager::Select @@ -388,7 +393,7 @@ void DolphinView::selectItems(const QRegExp& pattern, bool enabled) for (int index = 0; index < m_model->count(); index++) { const KFileItem item = m_model->fileItem(index); - if (pattern.exactMatch(item.text())) { + if (regexp.match(item.text()).hasMatch()) { // An alternative approach would be to store the matching items in a KItemSet and // select them in one go after the loop, but we'd need a new function // KItemListSelectionManager::setSelected(KItemSet, SelectionMode mode) @@ -404,7 +409,7 @@ void DolphinView::setZoomLevel(int level) m_view->setZoomLevel(level); if (zoomLevel() != oldZoomLevel) { hideToolTip(); - emit zoomLevelChanged(zoomLevel(), oldZoomLevel); + Q_EMIT zoomLevelChanged(zoomLevel(), oldZoomLevel); } } @@ -460,7 +465,7 @@ void DolphinView::setVisibleRoles(const QList& roles) m_visibleRoles = roles; m_view->setVisibleRoles(roles); - emit visibleRolesChanged(m_visibleRoles, previousRoles); + Q_EMIT visibleRolesChanged(m_visibleRoles, previousRoles); } QList DolphinView::visibleRoles() const @@ -474,10 +479,6 @@ void DolphinView::reload() QDataStream saveStream(&viewState, QIODevice::WriteOnly); saveState(saveStream); - const KFileItemList itemList = selectedItems(); - m_selectedUrls.clear(); - m_selectedUrls = KUrl::List(itemList.urlList()); - setUrl(url()); loadDirectory(url(), true); @@ -489,7 +490,7 @@ void DolphinView::readSettings() { const int oldZoomLevel = m_view->zoomLevel(); - GeneralSettings::self()->readConfig(); + GeneralSettings::self()->load(); m_view->readSettings(); applyViewProperties(); @@ -498,13 +499,13 @@ void DolphinView::readSettings() const int newZoomLevel = m_view->zoomLevel(); if (newZoomLevel != oldZoomLevel) { - emit zoomLevelChanged(newZoomLevel, oldZoomLevel); + Q_EMIT zoomLevelChanged(newZoomLevel, oldZoomLevel); } } void DolphinView::writeSettings() { - GeneralSettings::self()->writeConfig(); + GeneralSettings::self()->save(); m_view->writeSettings(); } @@ -528,20 +529,21 @@ QStringList DolphinView::mimeTypeFilters() const return m_model->mimeTypeFilters(); } -QString DolphinView::statusBarText() const +void DolphinView::requestStatusBarText() { - QString summary; - QString foldersText; - QString filesText; - - int folderCount = 0; - int fileCount = 0; - KIO::filesize_t totalFileSize = 0; + if (m_statJobForStatusBarText) { + // Kill the pending request. + m_statJobForStatusBarText->kill(); + } if (m_container->controller()->selectionManager()->hasSelection()) { + int folderCount = 0; + int fileCount = 0; + KIO::filesize_t totalFileSize = 0; + // Give a summary of the status of the selected files const KFileItemList list = selectedItems(); - foreach (const KFileItem& item, list) { + for (const KFileItem& item : list) { if (item.isDir()) { ++folderCount; } else { @@ -552,14 +554,37 @@ QString DolphinView::statusBarText() const if (folderCount + fileCount == 1) { // If only one item is selected, show info about it - return list.first().getStatusBarInfo(); + Q_EMIT statusBarTextChanged(list.first().getStatusBarInfo()); } else { // At least 2 items are selected - foldersText = i18ncp("@info:status", "1 Folder selected", "%1 Folders selected", folderCount); - filesText = i18ncp("@info:status", "1 File selected", "%1 Files selected", fileCount); + emitStatusBarText(folderCount, fileCount, totalFileSize, HasSelection); + } + } else { // has no selection + if (!m_model->rootItem().url().isValid()) { + return; } + + m_statJobForStatusBarText = KIO::statDetails(m_model->rootItem().url(), + KIO::StatJob::SourceSide, KIO::StatRecursiveSize, KIO::HideProgressInfo); + connect(m_statJobForStatusBarText, &KJob::result, + this, &DolphinView::slotStatJobResult); + m_statJobForStatusBarText->start(); + } +} + +void DolphinView::emitStatusBarText(const int folderCount, const int fileCount, + KIO::filesize_t totalFileSize, const Selection selection) +{ + QString foldersText; + QString filesText; + QString summary; + + if (selection == HasSelection) { + // At least 2 items are selected because the case of 1 selected item is handled in + // DolphinView::requestStatusBarText(). + foldersText = i18ncp("@info:status", "1 Folder selected", "%1 Folders selected", folderCount); + filesText = i18ncp("@info:status", "1 File selected", "%1 Files selected", fileCount); } else { - calculateItemCount(fileCount, folderCount, totalFileSize); foldersText = i18ncp("@info:status", "1 Folder", "%1 Folders", folderCount); filesText = i18ncp("@info:status", "1 File", "%1 Files", fileCount); } @@ -567,18 +592,17 @@ QString DolphinView::statusBarText() const if (fileCount > 0 && folderCount > 0) { summary = i18nc("@info:status folders, files (size)", "%1, %2 (%3)", foldersText, filesText, - KGlobal::locale()->formatByteSize(totalFileSize)); + KFormat().formatByteSize(totalFileSize)); } else if (fileCount > 0) { summary = i18nc("@info:status files (size)", "%1 (%2)", filesText, - KGlobal::locale()->formatByteSize(totalFileSize)); + KFormat().formatByteSize(totalFileSize)); } else if (folderCount > 0) { summary = foldersText; } else { summary = i18nc("@info:status", "0 Folders, 0 Files"); } - - return summary; + Q_EMIT statusBarTextChanged(summary); } QList DolphinView::versionControlActions(const KFileItemList& items) const @@ -597,7 +621,7 @@ QList DolphinView::versionControlActions(const KFileItemList& items) c return actions; } -void DolphinView::setUrl(const KUrl& url) +void DolphinView::setUrl(const QUrl& url) { if (url == m_url) { return; @@ -605,7 +629,6 @@ void DolphinView::setUrl(const KUrl& url) clearSelection(); - emit urlAboutToBeChanged(url); m_url = url; hideToolTip(); @@ -621,7 +644,7 @@ void DolphinView::setUrl(const KUrl& url) applyViewProperties(); loadDirectory(url); - emit urlChanged(url); + Q_EMIT urlChanged(url); } void DolphinView::selectAll() @@ -658,11 +681,11 @@ void DolphinView::renameSelectedItems() connect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); } else { - RenameDialog* dialog = new RenameDialog(this, items); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->show(); - dialog->raise(); - dialog->activateWindow(); + KIO::RenameFileDialog* dialog = new KIO::RenameFileDialog(items, this); + connect(dialog, &KIO::RenameFileDialog::renamingFinished, + this, &DolphinView::slotRenameDialogRenamingFinished); + + dialog->open(); } // Assure that the current index remains visible when KFileItemModel @@ -673,12 +696,12 @@ void DolphinView::renameSelectedItems() void DolphinView::trashSelectedItems() { - const KUrl::List list = simplifiedSelectedUrls(); + const QList list = simplifiedSelectedUrls(); KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(window()); if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::trash(list); - KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, list, KUrl("trash:/"), job); + KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, list, QUrl(QStringLiteral("trash:/")), job); KJobWidgets::setWindow(job, this); connect(job, &KIO::Job::result, this, &DolphinView::slotTrashFileFinished); @@ -687,7 +710,7 @@ void DolphinView::trashSelectedItems() void DolphinView::deleteSelectedItems() { - const KUrl::List list = simplifiedSelectedUrls(); + const QList list = simplifiedSelectedUrls(); KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(window()); @@ -699,19 +722,40 @@ void DolphinView::deleteSelectedItems() } } -void DolphinView::cutSelectedItems() +void DolphinView::cutSelectedItemsToClipboard() { QMimeData* mimeData = selectionMimeData(); KIO::setClipboardDataCut(mimeData, true); QApplication::clipboard()->setMimeData(mimeData); } -void DolphinView::copySelectedItems() +void DolphinView::copySelectedItemsToClipboard() { QMimeData* mimeData = selectionMimeData(); QApplication::clipboard()->setMimeData(mimeData); } +void DolphinView::copySelectedItems(const KFileItemList &selection, const QUrl &destinationUrl) +{ + KIO::CopyJob* job = KIO::copy(selection.urlList(), destinationUrl, KIO::DefaultFlags); + KJobWidgets::setWindow(job, this); + + connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult); + connect(job, &KIO::CopyJob::copyingDone, this, &DolphinView::slotCopyingDone); + KIO::FileUndoManager::self()->recordCopyJob(job); +} + +void DolphinView::moveSelectedItems(const KFileItemList &selection, const QUrl &destinationUrl) +{ + KIO::CopyJob* job = KIO::move(selection.urlList(), destinationUrl, KIO::DefaultFlags); + KJobWidgets::setWindow(job, this); + + connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult); + connect(job, &KIO::CopyJob::copyingDone, this, &DolphinView::slotCopyingDone); + KIO::FileUndoManager::self()->recordCopyJob(job); + +} + void DolphinView::paste() { pasteToUrl(url()); @@ -725,14 +769,104 @@ void DolphinView::pasteIntoFolder() } } +void DolphinView::duplicateSelectedItems() +{ + const KFileItemList itemList = selectedItems(); + if (itemList.isEmpty()) { + return; + } + + const QMimeDatabase db; + + // Duplicate all selected items and append "copy" to the end of the file name + // but before the filename extension, if present + QList newSelection; + for (const auto &item : itemList) { + const QUrl originalURL = item.url(); + const QString originalDirectoryPath = originalURL.adjusted(QUrl::RemoveFilename).path(); + const QString originalFileName = item.name(); + + QString extension = db.suffixForFileName(originalFileName); + + QUrl duplicateURL = originalURL; + + // No extension; new filename is " copy" + if (extension.isEmpty()) { + duplicateURL.setPath(originalDirectoryPath + i18nc(" copy", "%1 copy", originalFileName)); + // There's an extension; new filename is " copy." + } else { + // Need to add a dot since QMimeDatabase::suffixForFileName() doesn't include it + extension = QLatin1String(".") + extension; + const QString originalFilenameWithoutExtension = originalFileName.chopped(extension.size()); + // Preserve file's original filename extension in case the casing differs + // from what QMimeDatabase::suffixForFileName() returned + const QString originalExtension = originalFileName.right(extension.size()); + duplicateURL.setPath(originalDirectoryPath + i18nc(" copy", "%1 copy", originalFilenameWithoutExtension) + originalExtension); + } + + KIO::CopyJob* job = KIO::copyAs(originalURL, duplicateURL); + KJobWidgets::setWindow(job, this); + + if (job) { + newSelection << duplicateURL; + KIO::FileUndoManager::self()->recordCopyJob(job); + } + } + + forceUrlsSelection(newSelection.first(), newSelection); +} + void DolphinView::stopLoading() { m_model->cancelDirectoryLoading(); } +void DolphinView::updatePalette() +{ + QColor color = KColorScheme(isActiveWindow() ? QPalette::Active : QPalette::Inactive, KColorScheme::View).background().color(); + if (!m_active) { + color.setAlpha(150); + } + + QWidget* viewport = m_container->viewport(); + if (viewport) { + QPalette palette; + palette.setColor(viewport->backgroundRole(), color); + viewport->setPalette(palette); + } + + update(); +} + +void DolphinView::abortTwoClicksRenaming() +{ + m_twoClicksRenamingItemUrl.clear(); + m_twoClicksRenamingTimer->stop(); +} + bool DolphinView::eventFilter(QObject* watched, QEvent* event) { switch (event->type()) { + case QEvent::PaletteChange: + updatePalette(); + QPixmapCache::clear(); + break; + + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + updatePalette(); + break; + + case QEvent::KeyPress: + hideToolTip(ToolTipManager::HideBehavior::Instantly); + if (GeneralSettings::useTabForSwitchingSplitView()) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Tab && keyEvent->modifiers() == Qt::NoModifier) { + Q_EMIT toggleActiveViewRequested(); + return true; + } + } + break; case QEvent::FocusIn: if (watched == m_container) { setActive(true); @@ -742,6 +876,7 @@ bool DolphinView::eventFilter(QObject* watched, QEvent* event) case QEvent::GraphicsSceneDragEnter: if (watched == m_view) { m_dragging = true; + abortTwoClicksRenaming(); } break; @@ -765,10 +900,10 @@ bool DolphinView::eventFilter(QObject* watched, QEvent* event) void DolphinView::wheelEvent(QWheelEvent* event) { if (event->modifiers().testFlag(Qt::ControlModifier)) { - const int numDegrees = event->delta() / 8; - const int numSteps = numDegrees / 15; + const QPoint numDegrees = event->angleDelta() / 8; + const QPoint numSteps = numDegrees / 15; - setZoomLevel(zoomLevel() + numSteps); + setZoomLevel(zoomLevel() + numSteps.y()); event->accept(); } else { event->ignore(); @@ -783,13 +918,14 @@ void DolphinView::hideEvent(QHideEvent* event) bool DolphinView::event(QEvent* event) { - /* See Bug 297355 - * Dolphin leaves file preview tooltips open even when is not visible. - * - * Hide tool-tip when Dolphin loses focus. - */ if (event->type() == QEvent::WindowDeactivate) { + /* See Bug 297355 + * Dolphin leaves file preview tooltips open even when is not visible. + * + * Hide tool-tip when Dolphin loses focus. + */ hideToolTip(); + abortTwoClicksRenaming(); } return QWidget::event(event); @@ -802,9 +938,11 @@ void DolphinView::activate() void DolphinView::slotItemActivated(int index) { + abortTwoClicksRenaming(); + const KFileItem item = m_model->fileItem(index); if (!item.isNull()) { - emit itemActivated(item); + Q_EMIT itemActivated(item); } } @@ -812,6 +950,8 @@ void DolphinView::slotItemsActivated(const KItemSet& indexes) { Q_ASSERT(indexes.count() >= 2); + abortTwoClicksRenaming(); + 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); @@ -823,32 +963,32 @@ void DolphinView::slotItemsActivated(const KItemSet& indexes) KFileItemList items; items.reserve(indexes.count()); - foreach (int index, indexes) { + for (int index : indexes) { KFileItem item = m_model->fileItem(index); - const KUrl& url = openItemAsFolderUrl(item); + const QUrl& url = openItemAsFolderUrl(item); if (!url.isEmpty()) { // Open folders in new tabs - emit tabRequested(url); + Q_EMIT tabRequested(url); } else { items.append(item); } } if (items.count() == 1) { - emit itemActivated(items.first()); + Q_EMIT itemActivated(items.first()); } else if (items.count() > 1) { - emit itemsActivated(items); + Q_EMIT itemsActivated(items); } } void DolphinView::slotItemMiddleClicked(int index) { const KFileItem& item = m_model->fileItem(index); - const KUrl& url = openItemAsFolderUrl(item); + const QUrl& url = openItemAsFolderUrl(item); if (!url.isEmpty()) { - emit tabRequested(url); + Q_EMIT tabRequested(url); } else if (isTabsForFilesEnabled()) { - emit tabRequested(item.url()); + Q_EMIT tabRequested(item.url()); } } @@ -861,22 +1001,22 @@ void DolphinView::slotItemContextMenuRequested(int index, const QPointF& pos) } const KFileItem item = m_model->fileItem(index); - emit requestContextMenu(pos.toPoint(), item, url(), QList()); + Q_EMIT requestContextMenu(pos.toPoint(), item, url(), QList()); } void DolphinView::slotViewContextMenuRequested(const QPointF& pos) { - emit requestContextMenu(pos.toPoint(), KFileItem(), url(), QList()); + Q_EMIT requestContextMenu(pos.toPoint(), KFileItem(), url(), QList()); } void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) { ViewProperties props(viewPropertiesUrl()); - QPointer menu = new KMenu(QApplication::activeWindow()); + QPointer menu = new QMenu(QApplication::activeWindow()); KItemListView* view = m_container->controller()->view(); - const QSet visibleRolesSet = view->visibleRoles().toSet(); + const QList visibleRolesSet = view->visibleRoles(); bool indexingEnabled = false; #ifdef HAVE_BALOO @@ -885,18 +1025,18 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) #endif QString groupName; - QMenu* groupMenu = 0; + QMenu* groupMenu = nullptr; // Add all roles to the menu that can be shown or hidden by the user const QList rolesInfo = KFileItemModel::rolesInformation(); - foreach (const KFileItemModel::RoleInfo& info, rolesInfo) { + for (const KFileItemModel::RoleInfo& info : rolesInfo) { if (info.role == "text") { // It should not be possible to hide the "text" role continue; } const QString text = m_model->roleDescription(info.role); - QAction* action = 0; + QAction* action = nullptr; if (info.group.isEmpty()) { action = menu->addAction(text); } else { @@ -946,7 +1086,9 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) // Apply the current column-widths as custom column-widths and turn // off the automatic resizing of the columns QList columnWidths; - foreach (const QByteArray& role, view->visibleRoles()) { + const auto visibleRoles = view->visibleRoles(); + columnWidths.reserve(visibleRoles.count()); + for (const QByteArray& role : visibleRoles) { columnWidths.append(header->columnWidth(role)); } props.setHeaderColumnWidths(columnWidths); @@ -967,7 +1109,9 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) QList columnWidths; if (!header->automaticColumnResizing()) { - foreach (const QByteArray& role, view->visibleRoles()) { + const auto visibleRoles = view->visibleRoles(); + columnWidths.reserve(visibleRoles.count()); + for (const QByteArray& role : visibleRoles) { columnWidths.append(header->columnWidth(role)); } } @@ -978,10 +1122,8 @@ void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) delete menu; } -void DolphinView::slotHeaderColumnWidthChanged(const QByteArray& role, qreal current, qreal previous) +void DolphinView::slotHeaderColumnWidthChangeFinished(const QByteArray& role, qreal current) { - Q_UNUSED(previous); - const QList visibleRoles = m_view->visibleRoles(); ViewProperties props(viewPropertiesUrl()); @@ -990,7 +1132,7 @@ void DolphinView::slotHeaderColumnWidthChanged(const QByteArray& role, qreal cur columnWidths.clear(); columnWidths.reserve(visibleRoles.count()); const KItemListHeader* header = m_view->header(); - foreach (const QByteArray& role, visibleRoles) { + for (const QByteArray& role : visibleRoles) { const int width = header->columnWidth(role); columnWidths.append(width); } @@ -1012,22 +1154,24 @@ void DolphinView::slotItemHovered(int index) const QPoint pos = m_container->mapToGlobal(itemRect.topLeft().toPoint()); itemRect.moveTo(pos); - m_toolTipManager->showToolTip(item, itemRect); +#ifdef HAVE_BALOO + m_toolTipManager->showToolTip(item, itemRect, nativeParentWidget()->windowHandle()); +#endif } - emit requestItemInfo(item); + Q_EMIT requestItemInfo(item); } void DolphinView::slotItemUnhovered(int index) { - Q_UNUSED(index); + Q_UNUSED(index) hideToolTip(); - emit requestItemInfo(KFileItem()); + Q_EMIT requestItemInfo(KFileItem()); } void DolphinView::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event) { - KUrl destUrl; + QUrl destUrl; KFileItem destItem = m_model->fileItem(index); if (destItem.isNull() || (!destItem.isDir() && !destItem.isDesktopFile())) { // Use the URL of the view as drop target if the item is no directory @@ -1036,7 +1180,7 @@ void DolphinView::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* even destUrl = url(); } else { // The item represents a directory or desktop-file - destUrl = destItem.url(); + destUrl = destItem.mostLocalUrl(); } QDropEvent dropEvent(event->pos().toPoint(), @@ -1044,30 +1188,34 @@ void DolphinView::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* even event->mimeData(), event->buttons(), event->modifiers()); + dropUrls(destUrl, &dropEvent, this); - QString error; - KonqOperations* op = DragAndDropHelper::dropUrls(destItem, destUrl, &dropEvent, error); - if (!error.isEmpty()) { - emit infoMessage(error); - } + setActive(true); +} - if (op && destUrl == url()) { - // Mark the dropped urls as selected. - m_clearSelectionBeforeSelectingNewItems = true; - m_markFirstNewlySelectedItemAsCurrent = true; - connect(op, static_cast(&KonqOperations::aboutToCreate), this, &DolphinView::slotAboutToCreate); - } +void DolphinView::dropUrls(const QUrl &destUrl, QDropEvent *dropEvent, QWidget *dropWidget) +{ + KIO::DropJob* job = DragAndDropHelper::dropUrls(destUrl, dropEvent, dropWidget); - setActive(true); + if (job) { + connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult); + + if (destUrl == url()) { + // Mark the dropped urls as selected. + m_clearSelectionBeforeSelectingNewItems = true; + m_markFirstNewlySelectedItemAsCurrent = true; + connect(job, &KIO::DropJob::itemCreated, this, &DolphinView::slotItemCreated); + } + } } void DolphinView::slotModelChanged(KItemModelBase* current, KItemModelBase* previous) { - if (previous != 0) { + if (previous != nullptr) { Q_ASSERT(qobject_cast(previous)); KFileItemModel* fileItemModel = static_cast(previous); disconnect(fileItemModel, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); - m_versionControlObserver->setModel(0); + m_versionControlObserver->setModel(nullptr); } if (current) { @@ -1080,25 +1228,50 @@ void DolphinView::slotModelChanged(KItemModelBase* current, KItemModelBase* prev void DolphinView::slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons) { - Q_UNUSED(itemIndex); + Q_UNUSED(itemIndex) hideToolTip(); if (buttons & Qt::BackButton) { - emit goBackRequested(); + Q_EMIT goBackRequested(); } else if (buttons & Qt::ForwardButton) { - emit goForwardRequested(); + Q_EMIT goForwardRequested(); } } -void DolphinView::slotAboutToCreate(const KUrl::List& urls) +void DolphinView::slotSelectedItemTextPressed(int index) { - if (!urls.isEmpty()) { - if (m_markFirstNewlySelectedItemAsCurrent) { - markUrlAsCurrent(urls.first()); - m_markFirstNewlySelectedItemAsCurrent = false; + if (GeneralSettings::renameInline() && !m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { + const KFileItem item = m_model->fileItem(index); + const KFileItemListProperties capabilities(KFileItemList() << item); + if (capabilities.supportsMoving()) { + m_twoClicksRenamingItemUrl = item.url(); + m_twoClicksRenamingTimer->start(QApplication::doubleClickInterval()); } - m_selectedUrls << KUrl::List(KDirModel::simplifiedUrlList(urls)); + } +} + +void DolphinView::slotCopyingDone(KIO::Job *, const QUrl &, const QUrl &to) +{ + slotItemCreated(to); +} + +void DolphinView::slotItemCreated(const QUrl& url) +{ + if (m_markFirstNewlySelectedItemAsCurrent) { + markUrlAsCurrent(url); + m_markFirstNewlySelectedItemAsCurrent = false; + } + m_selectedUrls << url; +} + +void DolphinView::slotJobResult(KJob *job) +{ + if (job->error()) { + Q_EMIT errorMessage(job->errorString()); + } + if (!m_selectedUrls.isEmpty()) { + m_selectedUrls = KDirModel::simplifiedUrlList(m_selectedUrls); } } @@ -1119,7 +1292,37 @@ void DolphinView::slotSelectionChanged(const KItemSet& current, const KItemSet& void DolphinView::emitSelectionChangedSignal() { m_selectionChangedTimer->stop(); - emit selectionChanged(selectedItems()); + Q_EMIT selectionChanged(selectedItems()); +} + +void DolphinView::slotStatJobResult(KJob *job) +{ + int folderCount = 0; + int fileCount = 0; + KIO::filesize_t totalFileSize = 0; + bool countFileSize = true; + + const auto entry = static_cast(job)->statResult(); + if (entry.contains(KIO::UDSEntry::UDS_RECURSIVE_SIZE)) { + // We have a precomputed value. + totalFileSize = static_cast( + entry.numberValue(KIO::UDSEntry::UDS_RECURSIVE_SIZE)); + countFileSize = false; + } + + const int itemCount = m_model->count(); + for (int i = 0; i < itemCount; ++i) { + const KFileItem item = m_model->fileItem(i); + if (item.isDir()) { + ++folderCount; + } else { + ++fileCount; + if (countFileSize) { + totalFileSize += item.size(); + } + } + } + emitStatusBarText(folderCount, fileCount, totalFileSize, NoSelection); } void DolphinView::updateSortRole(const QByteArray& role) @@ -1130,7 +1333,7 @@ void DolphinView::updateSortRole(const QByteArray& role) KItemModelBase* model = m_container->controller()->model(); model->setSortRole(role); - emit sortRoleChanged(role); + Q_EMIT sortRoleChanged(role); } void DolphinView::updateSortOrder(Qt::SortOrder order) @@ -1140,7 +1343,7 @@ void DolphinView::updateSortOrder(Qt::SortOrder order) m_model->setSortOrder(order); - emit sortOrderChanged(order); + Q_EMIT sortOrderChanged(order); } void DolphinView::updateSortFoldersFirst(bool foldersFirst) @@ -1150,12 +1353,15 @@ void DolphinView::updateSortFoldersFirst(bool foldersFirst) m_model->setSortDirectoriesFirst(foldersFirst); - emit sortFoldersFirstChanged(foldersFirst); + Q_EMIT sortFoldersFirstChanged(foldersFirst); } QPair DolphinView::pasteInfo() const { - return KonqOperations::pasteInfo(url()); + const QMimeData *mimeData = QApplication::clipboard()->mimeData(); + QPair info; + info.second = KIO::pasteActionText(mimeData, &info.first, rootItem()); + return info; } void DolphinView::setTabsForFilesEnabled(bool tabsForFiles) @@ -1186,11 +1392,14 @@ void DolphinView::restoreState(QDataStream& stream) // Restore the current item that had the keyboard focus stream >> m_currentItemUrl; + // Restore the previously selected items + stream >> m_selectedUrls; + // Restore the view position stream >> m_restoredContentsPosition; // Restore expanded folders (only relevant for the details view - will be ignored by the view in other view modes) - QSet urls; + QSet urls; stream >> urls; m_model->restoreExpandedDirectories(urls); } @@ -1204,12 +1413,15 @@ void DolphinView::saveState(QDataStream& stream) if (currentIndex != -1) { KFileItem item = m_model->fileItem(currentIndex); Q_ASSERT(!item.isNull()); // If the current index is valid a item must exist - KUrl currentItemUrl = item.url(); + QUrl currentItemUrl = item.url(); stream << currentItemUrl; } else { - stream << KUrl(); + stream << QUrl(); } + // Save the selected urls + stream << selectedItems().urlList(); + // Save view position const qreal x = m_container->horizontalScrollBar()->value(); const qreal y = m_container->verticalScrollBar()->value(); @@ -1234,13 +1446,13 @@ QString DolphinView::viewPropertiesContext() const return m_viewPropertiesContext; } -KUrl DolphinView::openItemAsFolderUrl(const KFileItem& item, const bool browseThroughArchives) +QUrl DolphinView::openItemAsFolderUrl(const KFileItem& item, const bool browseThroughArchives) { if (item.isNull()) { - return KUrl(); + return QUrl(); } - KUrl url = item.targetUrl(); + QUrl url = item.targetUrl(); if (item.isDir()) { return url; @@ -1257,7 +1469,7 @@ KUrl DolphinView::openItemAsFolderUrl(const KFileItem& item, const bool browseTh // open OpenDocument files as zip folders... const QString& protocol = KProtocolManager::protocolForArchiveMimetype(mimetype); if (!protocol.isEmpty()) { - url.setProtocol(protocol); + url.setScheme(protocol); return url; } } @@ -1268,50 +1480,66 @@ KUrl DolphinView::openItemAsFolderUrl(const KFileItem& item, const bool browseTh if (desktopFile.hasLinkType()) { const QString linkUrl = desktopFile.readUrl(); if (!linkUrl.startsWith(QLatin1String("http"))) { - return linkUrl; + return QUrl::fromUserInput(linkUrl); } } } } - return KUrl(); + return QUrl(); +} + +void DolphinView::resetZoomLevel() +{ + ViewModeSettings::ViewMode mode; + + switch (m_mode) { + case IconsView: mode = ViewModeSettings::IconsMode; break; + case CompactView: mode = ViewModeSettings::CompactMode; break; + case DetailsView: mode = ViewModeSettings::DetailsMode; break; + } + const ViewModeSettings settings(mode); + const QSize iconSize = QSize(settings.iconSize(), settings.iconSize()); + setZoomLevel(ZoomLevelInfo::zoomLevelForIconSize(iconSize)); } -void DolphinView::observeCreatedItem(const KUrl& url) +void DolphinView::observeCreatedItem(const QUrl& url) { if (m_active) { - clearSelection(); - markUrlAsCurrent(url); - markUrlsAsSelected(QList() << url); + forceUrlsSelection(url, {url}); } } -void DolphinView::slotDirectoryRedirection(const KUrl& oldUrl, const KUrl& newUrl) +void DolphinView::slotDirectoryRedirection(const QUrl& oldUrl, const QUrl& newUrl) { - if (oldUrl.equals(url(), KUrl::CompareWithoutTrailingSlash)) { - emit redirection(oldUrl, newUrl); + if (oldUrl.matches(url(), QUrl::StripTrailingSlash)) { + Q_EMIT redirection(oldUrl, newUrl); m_url = newUrl; // #186947 } } void DolphinView::updateViewState() { - if (m_currentItemUrl != KUrl()) { + if (m_currentItemUrl != QUrl()) { KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); - const int currentIndex = m_model->index(m_currentItemUrl); - if (currentIndex != -1) { - selectionManager->setCurrentItem(currentIndex); - - // scroll to current item and reset the state - if (m_scrollToCurrentItem) { - m_view->scrollToItem(currentIndex); - m_scrollToCurrentItem = false; + + // if there is a selection already, leave it that way + if (!selectionManager->hasSelection()) { + const int currentIndex = m_model->index(m_currentItemUrl); + if (currentIndex != -1) { + selectionManager->setCurrentItem(currentIndex); + + // scroll to current item and reset the state + if (m_scrollToCurrentItem) { + m_view->scrollToItem(currentIndex); + m_scrollToCurrentItem = false; + } + } else { + selectionManager->setCurrentItem(0); } - } else { - selectionManager->setCurrentItem(0); } - m_currentItemUrl = KUrl(); + m_currentItemUrl = QUrl(); } if (!m_restoredContentsPosition.isNull()) { @@ -1326,47 +1554,55 @@ void DolphinView::updateViewState() if (!m_selectedUrls.isEmpty()) { KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); - if (m_clearSelectionBeforeSelectingNewItems) { - selectionManager->clearSelection(); - m_clearSelectionBeforeSelectingNewItems = false; - } + // if there is a selection already, leave it that way + if (!selectionManager->hasSelection()) { + if (m_clearSelectionBeforeSelectingNewItems) { + selectionManager->clearSelection(); + m_clearSelectionBeforeSelectingNewItems = false; + } - KItemSet selectedItems = selectionManager->selectedItems(); + KItemSet selectedItems = selectionManager->selectedItems(); - QList::iterator it = m_selectedUrls.begin(); - while (it != m_selectedUrls.end()) { - const int index = m_model->index(*it); - if (index >= 0) { - selectedItems.insert(index); - it = m_selectedUrls.erase(it); - } else { - ++it; + QList::iterator it = m_selectedUrls.begin(); + while (it != m_selectedUrls.end()) { + const int index = m_model->index(*it); + if (index >= 0) { + selectedItems.insert(index); + it = m_selectedUrls.erase(it); + } else { + ++it; + } } - } - selectionManager->setSelectedItems(selectedItems); + selectionManager->beginAnchoredSelection(selectionManager->currentItem()); + selectionManager->setSelectedItems(selectedItems); + } } } -void DolphinView::hideToolTip() +void DolphinView::hideToolTip(const ToolTipManager::HideBehavior behavior) { +#ifdef HAVE_BALOO if (GeneralSettings::showToolTips()) { - m_toolTipManager->hideToolTip(); + m_toolTipManager->hideToolTip(behavior); } +#else + Q_UNUSED(behavior) +#endif } -void DolphinView::calculateItemCount(int& fileCount, - int& folderCount, - KIO::filesize_t& totalFileSize) const +void DolphinView::slotTwoClicksRenamingTimerTimeout() { - const int itemCount = m_model->count(); - for (int i = 0; i < itemCount; ++i) { - const KFileItem item = m_model->fileItem(i); - if (item.isDir()) { - ++folderCount; - } else { - ++fileCount; - totalFileSize += item.size(); + const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); + + // verify that only one item is selected + if (selectionManager->selectedItems().count() == 1) { + const int index = selectionManager->currentItem(); + const QUrl fileItemUrl = m_model->fileItem(index).url(); + + // check if the selected item was the same item that started the twoClicksRenaming + if (fileItemUrl.isValid() && m_twoClicksRenamingItemUrl == fileItemUrl) { + renameSelectedItems(); } } } @@ -1374,54 +1610,78 @@ void DolphinView::calculateItemCount(int& fileCount, void DolphinView::slotTrashFileFinished(KJob* job) { if (job->error() == 0) { - emit operationCompletedMessage(i18nc("@info:status", "Trash operation completed.")); + Q_EMIT operationCompletedMessage(i18nc("@info:status", "Trash operation completed.")); } else if (job->error() != KIO::ERR_USER_CANCELED) { - emit errorMessage(job->errorString()); + Q_EMIT errorMessage(job->errorString()); } } void DolphinView::slotDeleteFileFinished(KJob* job) { if (job->error() == 0) { - emit operationCompletedMessage(i18nc("@info:status", "Delete operation completed.")); + Q_EMIT operationCompletedMessage(i18nc("@info:status", "Delete operation completed.")); } else if (job->error() != KIO::ERR_USER_CANCELED) { - emit errorMessage(job->errorString()); + Q_EMIT errorMessage(job->errorString()); } } -void DolphinView::slotRenamingFailed(const KUrl& oldUrl, const KUrl& newUrl) +void DolphinView::slotRenamingResult(KJob* job) { - const int index = m_model->index(newUrl); - if (index >= 0) { - QHash data; - data.insert("text", oldUrl.fileName()); - m_model->setData(index, data); + if (job->error()) { + KIO::CopyJob *copyJob = qobject_cast(job); + Q_ASSERT(copyJob); + const QUrl newUrl = copyJob->destUrl(); + const int index = m_model->index(newUrl); + if (index >= 0) { + QHash data; + const QUrl oldUrl = copyJob->srcUrls().at(0); + data.insert("text", oldUrl.fileName()); + m_model->setData(index, data); + } } } void DolphinView::slotDirectoryLoadingStarted() { + m_loading = true; + updatePlaceholderLabel(); + // Disable the writestate temporary until it can be determined in a fast way - // in DolphinView::slotLoadingCompleted() + // in DolphinView::slotDirectoryLoadingCompleted() if (m_isFolderWritable) { m_isFolderWritable = false; - emit writeStateChanged(m_isFolderWritable); + Q_EMIT writeStateChanged(m_isFolderWritable); } - emit directoryLoadingStarted(); + Q_EMIT directoryLoadingStarted(); } void DolphinView::slotDirectoryLoadingCompleted() { + m_loading = false; + // Update the view-state. This has to be done asynchronously // because the view might not be in its final state yet. - QTimer::singleShot(0, this, SLOT(updateViewState())); + QTimer::singleShot(0, this, &DolphinView::updateViewState); - emit directoryLoadingCompleted(); + // Update the placeholder label in case we found that the folder was empty + // after loading it + Q_EMIT directoryLoadingCompleted(); + + updatePlaceholderLabel(); updateWritableState(); } +void DolphinView::slotDirectoryLoadingCanceled() +{ + m_loading = false; + + updatePlaceholderLabel(); + + Q_EMIT directoryLoadingCanceled(); +} + void DolphinView::slotItemsChanged() { m_assureVisibleCurrentIndex = false; @@ -1429,30 +1689,30 @@ void DolphinView::slotItemsChanged() void DolphinView::slotSortOrderChangedByHeader(Qt::SortOrder current, Qt::SortOrder previous) { - Q_UNUSED(previous); + Q_UNUSED(previous) Q_ASSERT(m_model->sortOrder() == current); ViewProperties props(viewPropertiesUrl()); props.setSortOrder(current); - emit sortOrderChanged(current); + Q_EMIT sortOrderChanged(current); } void DolphinView::slotSortRoleChangedByHeader(const QByteArray& current, const QByteArray& previous) { - Q_UNUSED(previous); + Q_UNUSED(previous) Q_ASSERT(m_model->sortRole() == current); ViewProperties props(viewPropertiesUrl()); props.setSortRole(current); - emit sortRoleChanged(current); + Q_EMIT sortRoleChanged(current); } void DolphinView::slotVisibleRolesChangedByHeader(const QList& current, const QList& previous) { - Q_UNUSED(previous); + Q_UNUSED(previous) Q_ASSERT(m_container->controller()->view()->visibleRoles() == current); const QList previousVisibleRoles = m_visibleRoles; @@ -1462,7 +1722,7 @@ void DolphinView::slotVisibleRolesChangedByHeader(const QList& curre ViewProperties props(viewPropertiesUrl()); props.setVisibleRoles(m_visibleRoles); - emit visibleRolesChanged(m_visibleRoles, previousVisibleRoles); + Q_EMIT visibleRolesChanged(m_visibleRoles, previousVisibleRoles); } void DolphinView::slotRoleEditingCanceled() @@ -1483,15 +1743,40 @@ void DolphinView::slotRoleEditingFinished(int index, const QByteArray& role, con if (role == "text") { const KFileItem oldItem = m_model->fileItem(index); const QString newName = value.toString(); - if (!newName.isEmpty() && newName != oldItem.text() && newName != QLatin1String(".") && newName != QLatin1String("..")) { - const KUrl oldUrl = oldItem.url(); + if (!newName.isEmpty() && newName != oldItem.text() && newName != QLatin1Char('.') && newName != QLatin1String("..")) { + const QUrl oldUrl = oldItem.url(); + + QUrl newUrl = oldUrl.adjusted(QUrl::RemoveFilename); + newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName)); + +#ifndef Q_OS_WIN + //Confirm hiding file/directory by renaming inline + if (!hiddenFilesShown() && newName.startsWith(QLatin1Char('.')) && !oldItem.name().startsWith(QLatin1Char('.'))) { + KGuiItem yesGuiItem(KStandardGuiItem::yes()); + yesGuiItem.setText(i18nc("@action:button", "Rename and Hide")); + + const auto code = KMessageBox::questionYesNo(this, + oldItem.isFile() ? i18n("Adding a dot to the beginning of this file's name will hide it from view.\n" + "Do you still want to rename it?") + : i18n("Adding a dot to the beginning of this folder's name will hide it from view.\n" + "Do you still want to rename it?"), + oldItem.isFile() ? i18n("Hide this File?") : i18n("Hide this Folder?"), + yesGuiItem, + KStandardGuiItem::cancel(), + QStringLiteral("ConfirmHide") + ); + + if (code == KMessageBox::No) { + return; + } + } +#endif - const KUrl newUrl(url().path(KUrl::AddTrailingSlash) + newName); const bool newNameExistsAlready = (m_model->index(newUrl) >= 0); if (!newNameExistsAlready) { // Only change the data in the model if no item with the new name // is in the model yet. If there is an item with the new name - // already, calling KonqOperations::rename() will open a dialog + // already, calling KIO::CopyJob will open a dialog // asking for a new name, and KFileItemModel will update the // data when the dir lister signals that the file name has changed. QHash data; @@ -1499,24 +1784,30 @@ void DolphinView::slotRoleEditingFinished(int index, const QByteArray& role, con m_model->setData(index, data); } - KonqOperations* op = KonqOperations::renameV2(this, oldUrl, newName); - if (op && !newNameExistsAlready) { - // Only connect the renamingFailed signal if there is no item with the new name + KIO::Job * job = KIO::moveAs(oldUrl, newUrl); + KJobWidgets::setWindow(job, this); + KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); + job->uiDelegate()->setAutoErrorHandlingEnabled(true); + + forceUrlsSelection(newUrl, {newUrl}); + + if (!newNameExistsAlready) { + // Only connect the result signal if there is no item with the new name // in the model yet, see bug 328262. - connect(op, &KonqOperations::renamingFailed, this, &DolphinView::slotRenamingFailed); + connect(job, &KJob::result, this, &DolphinView::slotRenamingResult); } } } } -void DolphinView::loadDirectory(const KUrl& url, bool reload) +void DolphinView::loadDirectory(const QUrl& url, bool reload) { if (!url.isValid()) { - const QString location(url.pathOrUrl()); + const QString location(url.toDisplayString(QUrl::PreferLocalFile)); if (location.isEmpty()) { - emit errorMessage(i18nc("@info:status", "The location is empty.")); + Q_EMIT errorMessage(i18nc("@info:status", "The location is empty.")); } else { - emit errorMessage(i18nc("@info:status", "The location '%1' is invalid.", location)); + Q_EMIT errorMessage(i18nc("@info:status", "The location '%1' is invalid.", location)); } return; } @@ -1549,41 +1840,41 @@ void DolphinView::applyViewProperties(const ViewProperties& props) const int oldZoomLevel = m_view->zoomLevel(); applyModeToView(); - emit modeChanged(m_mode, previousMode); + Q_EMIT modeChanged(m_mode, previousMode); if (m_view->zoomLevel() != oldZoomLevel) { - emit zoomLevelChanged(m_view->zoomLevel(), oldZoomLevel); + Q_EMIT zoomLevelChanged(m_view->zoomLevel(), oldZoomLevel); } } const bool hiddenFilesShown = props.hiddenFilesShown(); if (hiddenFilesShown != m_model->showHiddenFiles()) { m_model->setShowHiddenFiles(hiddenFilesShown); - emit hiddenFilesShownChanged(hiddenFilesShown); + Q_EMIT hiddenFilesShownChanged(hiddenFilesShown); } const bool groupedSorting = props.groupedSorting(); if (groupedSorting != m_model->groupedSorting()) { m_model->setGroupedSorting(groupedSorting); - emit groupedSortingChanged(groupedSorting); + Q_EMIT groupedSortingChanged(groupedSorting); } const QByteArray sortRole = props.sortRole(); if (sortRole != m_model->sortRole()) { m_model->setSortRole(sortRole); - emit sortRoleChanged(sortRole); + Q_EMIT sortRoleChanged(sortRole); } const Qt::SortOrder sortOrder = props.sortOrder(); if (sortOrder != m_model->sortOrder()) { m_model->setSortOrder(sortOrder); - emit sortOrderChanged(sortOrder); + Q_EMIT sortOrderChanged(sortOrder); } const bool sortFoldersFirst = props.sortFoldersFirst(); if (sortFoldersFirst != m_model->sortDirectoriesFirst()) { m_model->setSortDirectoriesFirst(sortFoldersFirst); - emit sortFoldersFirstChanged(sortFoldersFirst); + Q_EMIT sortFoldersFirstChanged(sortFoldersFirst); } const QList visibleRoles = props.visibleRoles(); @@ -1591,7 +1882,7 @@ void DolphinView::applyViewProperties(const ViewProperties& props) const QList previousVisibleRoles = m_visibleRoles; m_visibleRoles = visibleRoles; m_view->setVisibleRoles(visibleRoles); - emit visibleRolesChanged(m_visibleRoles, previousVisibleRoles); + Q_EMIT visibleRolesChanged(m_visibleRoles, previousVisibleRoles); } const bool previewsShown = props.previewsShown(); @@ -1599,11 +1890,11 @@ void DolphinView::applyViewProperties(const ViewProperties& props) const int oldZoomLevel = zoomLevel(); m_view->setPreviewsShown(previewsShown); - emit previewsShownChanged(previewsShown); + Q_EMIT previewsShownChanged(previewsShown); // Changing the preview-state might result in a changed zoom-level if (oldZoomLevel != zoomLevel()) { - emit zoomLevelChanged(zoomLevel(), oldZoomLevel); + Q_EMIT zoomLevelChanged(zoomLevel(), oldZoomLevel); } } @@ -1638,22 +1929,23 @@ void DolphinView::applyModeToView() } } -void DolphinView::pasteToUrl(const KUrl& url) +void DolphinView::pasteToUrl(const QUrl& url) { - KonqOperations* op = KonqOperations::doPasteV2(this, url); - if (op) { - m_clearSelectionBeforeSelectingNewItems = true; - m_markFirstNewlySelectedItemAsCurrent = true; - connect(op, static_cast(&KonqOperations::aboutToCreate), this, &DolphinView::slotAboutToCreate); - } + KIO::PasteJob *job = KIO::paste(QApplication::clipboard()->mimeData(), url); + KJobWidgets::setWindow(job, this); + m_clearSelectionBeforeSelectingNewItems = true; + m_markFirstNewlySelectedItemAsCurrent = true; + connect(job, &KIO::PasteJob::itemCreated, this, &DolphinView::slotItemCreated); + connect(job, &KIO::PasteJob::result, this, &DolphinView::slotJobResult); } -KUrl::List DolphinView::simplifiedSelectedUrls() const +QList DolphinView::simplifiedSelectedUrls() const { - KUrl::List urls; + QList urls; const KFileItemList items = selectedItems(); - foreach (const KFileItem& item, items) { + urls.reserve(items.count()); + for (const KFileItem& item : items) { urls.append(item.url()); } @@ -1682,26 +1974,104 @@ void DolphinView::updateWritableState() if (item.isNull()) { // Try to find out if the URL is writable even if the "root item" is // null, see https://bugs.kde.org/show_bug.cgi?id=330001 - item = KFileItem(KFileItem::Unknown, KFileItem::Unknown, url(), true); + item = KFileItem(url()); + item.setDelayedMimeTypes(true); } KFileItemListProperties capabilities(KFileItemList() << item); m_isFolderWritable = capabilities.supportsWriting(); if (m_isFolderWritable != wasFolderWritable) { - emit writeStateChanged(m_isFolderWritable); + Q_EMIT writeStateChanged(m_isFolderWritable); } } -KUrl DolphinView::viewPropertiesUrl() const +QUrl DolphinView::viewPropertiesUrl() const { if (m_viewPropertiesContext.isEmpty()) { return m_url; } - KUrl url; - url.setProtocol(m_url.protocol()); + QUrl url; + url.setScheme(m_url.scheme()); url.setPath(m_viewPropertiesContext); return url; } +void DolphinView::slotRenameDialogRenamingFinished(const QList& urls) +{ + forceUrlsSelection(urls.first(), urls); +} + +void DolphinView::forceUrlsSelection(const QUrl& current, const QList& selected) +{ + clearSelection(); + m_clearSelectionBeforeSelectingNewItems = true; + markUrlAsCurrent(current); + markUrlsAsSelected(selected); +} + +void DolphinView::copyPathToClipboard() +{ + const KFileItemList list = selectedItems(); + if (list.isEmpty()) { + return; + } + const KFileItem& item = list.at(0); + QString path = item.localPath(); + if (path.isEmpty()) { + path = item.url().toDisplayString(); + } + QClipboard* clipboard = QApplication::clipboard(); + if (clipboard == nullptr) { + return; + } + clipboard->setText(path); +} + +void DolphinView::slotIncreaseZoom() +{ + setZoomLevel(zoomLevel() + 1); +} + +void DolphinView::slotDecreaseZoom() +{ + setZoomLevel(zoomLevel() - 1); +} + +void DolphinView::slotSwipeUp() +{ + Q_EMIT goUpRequested(); +} + +void DolphinView::updatePlaceholderLabel() +{ + if (m_loading || itemsCount() > 0) { + m_placeholderLabel->setVisible(false); + return; + } + + if (!nameFilter().isEmpty()) { + m_placeholderLabel->setText(i18n("No items matching the filter")); + } else if (m_url.scheme() == QLatin1String("baloosearch") || m_url.scheme() == QLatin1String("filenamesearch")) { + m_placeholderLabel->setText(i18n("No items matching the search")); + } else if (m_url.scheme() == QLatin1String("trash")) { + m_placeholderLabel->setText(i18n("Trash is empty")); + } else if (m_url.scheme() == QLatin1String("tags")) { + m_placeholderLabel->setText(i18n("No tags")); + } else if (m_url.scheme() == QLatin1String("recentlyused")) { + m_placeholderLabel->setText(i18n("No recently used items")); + } else if (m_url.scheme() == QLatin1String("smb")) { + m_placeholderLabel->setText(i18n("No shared folders found")); + } else if (m_url.scheme() == QLatin1String("network")) { + m_placeholderLabel->setText(i18n("No relevant network resources found")); + } else if (m_url.scheme() == QLatin1String("mtp")) { + m_placeholderLabel->setText(i18n("No MTP-compatible devices found")); + } else if (m_url.scheme() == QLatin1String("bluetooth")) { + m_placeholderLabel->setText(i18n("No Bluetooth devices found")); + } else { + m_placeholderLabel->setText(i18n("Folder is empty")); + } + + m_placeholderLabel->setVisible(true); +}