diff --git a/src/dolphinremoveaction.cpp b/src/dolphinremoveaction.cpp index ce3059934..ab1117770 100644 --- a/src/dolphinremoveaction.cpp +++ b/src/dolphinremoveaction.cpp @@ -1,72 +1,78 @@ /*************************************************************************** * Copyright (C) 2013 by Dawit Alemayehu * * Copyright (C) 2017 by Elvis Angelaccio * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "dolphinremoveaction.h" #include #include DolphinRemoveAction::DolphinRemoveAction(QObject* parent, KActionCollection* collection) : QAction(parent), m_collection(collection) { update(); connect(this, &DolphinRemoveAction::triggered, this, &DolphinRemoveAction::slotRemoveActionTriggered); } void DolphinRemoveAction::slotRemoveActionTriggered() { if (m_action) { m_action->trigger(); } } void DolphinRemoveAction::update(ShiftState shiftState) { if (!m_collection) { m_action = nullptr; return; } if (shiftState == ShiftState::Unknown) { shiftState = QGuiApplication::keyboardModifiers() & Qt::ShiftModifier ? ShiftState::Pressed : ShiftState::Released; } switch (shiftState) { - case ShiftState::Pressed: + case ShiftState::Pressed: { m_action = m_collection->action(KStandardAction::name(KStandardAction::DeleteFile)); + // Make sure we show Shift+Del in the context menu. + auto deleteShortcuts = m_action->shortcuts(); + deleteShortcuts.removeAll(Qt::SHIFT | Qt::Key_Delete); + deleteShortcuts.prepend(Qt::SHIFT | Qt::Key_Delete); + m_collection->setDefaultShortcuts(this, deleteShortcuts); break; + } case ShiftState::Released: m_action = m_collection->action(KStandardAction::name(KStandardAction::MoveToTrash)); + m_collection->setDefaultShortcuts(this, m_action->shortcuts()); break; case ShiftState::Unknown: Q_UNREACHABLE(); break; } if (m_action) { setText(m_action->text()); setIcon(m_action->icon()); - m_collection->setDefaultShortcuts(this, m_action->shortcuts()); setEnabled(m_action->isEnabled()); } } diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index 5fcec9241..2ca51f52d 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -1,1749 +1,1765 @@ /*************************************************************************** * 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 * ***************************************************************************/ #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 "dolphinnewfilemenuobserver.h" #include "dolphin_detailsmodesettings.h" #include "dolphin_generalsettings.h" #include "dolphinitemlistview.h" #include "draganddrophelper.h" #include "renamedialog.h" #include "versioncontrol/versioncontrolobserver.h" #include "viewmodecontroller.h" #include "viewproperties.h" #include "views/tooltips/tooltipmanager.h" #include "zoomlevelinfo.h" #ifdef HAVE_BALOO #include #endif #include 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_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_currentItemUrl(), m_scrollToCurrentItem(false), m_restoredContentsPosition(), m_selectedUrls(), m_clearSelectionBeforeSelectingNewItems(false), m_markFirstNewlySelectedItemAsCurrent(false), m_versionControlObserver(0) { m_topLayout = new QVBoxLayout(this); m_topLayout->setSpacing(0); m_topLayout->setMargin(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 // creation is done asynchronously, several signals must be checked: connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::itemCreated, this, &DolphinView::observeCreatedItem); m_selectionChangedTimer = new QTimer(this); m_selectionChangedTimer->setSingleShot(true); m_selectionChangedTimer->setInterval(300); connect(m_selectionChangedTimer, &QTimer::timeout, this, &DolphinView::emitSelectionChangedSignal); m_model = new KFileItemModel(this); m_view = new DolphinItemListView(); m_view->setEnabledSelectionToggles(GeneralSettings::showSelectionToggle()); m_view->setVisibleRoles({"text"}); applyModeToView(); KItemListController* controller = new KItemListController(m_model, m_view, this); const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1; controller->setAutoActivationDelay(delay); // The EnlargeSmallPreviews setting can only be changed after the model // has been set in the view by KItemListController. m_view->setEnlargeSmallPreviews(GeneralSettings::enlargeSmallPreviews()); 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); controller->setSelectionBehavior(KItemListController::MultiSelection); connect(controller, &KItemListController::itemActivated, this, &DolphinView::slotItemActivated); connect(controller, &KItemListController::itemsActivated, this, &DolphinView::slotItemsActivated); connect(controller, &KItemListController::itemMiddleClicked, this, &DolphinView::slotItemMiddleClicked); connect(controller, &KItemListController::itemContextMenuRequested, this, &DolphinView::slotItemContextMenuRequested); connect(controller, &KItemListController::viewContextMenuRequested, this, &DolphinView::slotViewContextMenuRequested); connect(controller, &KItemListController::headerContextMenuRequested, this, &DolphinView::slotHeaderContextMenuRequested); connect(controller, &KItemListController::mouseButtonPressed, this, &DolphinView::slotMouseButtonPressed); connect(controller, &KItemListController::itemHovered, this, &DolphinView::slotItemHovered); connect(controller, &KItemListController::itemUnhovered, this, &DolphinView::slotItemUnhovered); connect(controller, &KItemListController::itemDropEvent, this, &DolphinView::slotItemDropEvent); connect(controller, &KItemListController::escapePressed, this, &DolphinView::stopLoading); connect(controller, &KItemListController::modelChanged, this, &DolphinView::slotModelChanged); 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::directoryLoadingProgress, this, &DolphinView::directoryLoadingProgress); connect(m_model, &KFileItemModel::directorySortingProgress, this, &DolphinView::directorySortingProgress); connect(m_model, &KFileItemModel::itemsChanged, this, &DolphinView::slotItemsChanged); connect(m_model, &KFileItemModel::itemsRemoved, this, &DolphinView::itemCountChanged); connect(m_model, &KFileItemModel::itemsInserted, this, &DolphinView::itemCountChanged); connect(m_model, &KFileItemModel::infoMessage, this, &DolphinView::infoMessage); connect(m_model, &KFileItemModel::errorMessage, this, &DolphinView::errorMessage); connect(m_model, &KFileItemModel::directoryRedirection, this, &DolphinView::slotDirectoryRedirection); connect(m_model, &KFileItemModel::urlIsFileError, this, &DolphinView::urlIsFileError); m_view->installEventFilter(this); connect(m_view, &DolphinItemListView::sortOrderChanged, this, &DolphinView::slotSortOrderChangedByHeader); connect(m_view, &DolphinItemListView::sortRoleChanged, this, &DolphinView::slotSortRoleChangedByHeader); connect(m_view, &DolphinItemListView::visibleRolesChanged, this, &DolphinView::slotVisibleRolesChangedByHeader); connect(m_view, &DolphinItemListView::roleEditingCanceled, this, &DolphinView::slotRoleEditingCanceled); connect(m_view->header(), &KItemListHeader::columnWidthChangeFinished, this, &DolphinView::slotHeaderColumnWidthChangeFinished); KItemListSelectionManager* selectionManager = controller->selectionManager(); connect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &DolphinView::slotSelectionChanged); m_toolTipManager = new ToolTipManager(this); connect(m_toolTipManager, &ToolTipManager::urlActivated, this, &DolphinView::urlActivated); m_versionControlObserver = new VersionControlObserver(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); applyViewProperties(); m_topLayout->addWidget(m_container); loadDirectory(url); } DolphinView::~DolphinView() { } QUrl DolphinView::url() const { return m_url; } void DolphinView::setActive(bool active) { if (active == m_active) { return; } m_active = active; updatePalette(); if (active) { m_container->setFocus(); emit activated(); emit writeStateChanged(m_isFolderWritable); } } bool DolphinView::isActive() const { return m_active; } void DolphinView::setMode(Mode mode) { if (mode != m_mode) { ViewProperties props(viewPropertiesUrl()); props.setViewMode(mode); // We pass the new ViewProperties to applyViewProperties, rather than // storing them on disk and letting applyViewProperties() read them // from there, to prevent that changing the view mode fails if the // .directory file is not writable (see bug 318534). applyViewProperties(props); } } DolphinView::Mode DolphinView::mode() const { return m_mode; } void DolphinView::setPreviewsShown(bool show) { if (previewsShown() == show) { return; } ViewProperties props(viewPropertiesUrl()); props.setPreviewsShown(show); const int oldZoomLevel = m_view->zoomLevel(); m_view->setPreviewsShown(show); emit previewsShownChanged(show); const int newZoomLevel = m_view->zoomLevel(); if (newZoomLevel != oldZoomLevel) { emit zoomLevelChanged(newZoomLevel, oldZoomLevel); } } bool DolphinView::previewsShown() const { return m_view->previewsShown(); } void DolphinView::setHiddenFilesShown(bool show) { if (m_model->showHiddenFiles() == show) { return; } const KFileItemList itemList = selectedItems(); m_selectedUrls.clear(); m_selectedUrls = itemList.urlList(); ViewProperties props(viewPropertiesUrl()); props.setHiddenFilesShown(show); m_model->setShowHiddenFiles(show); emit hiddenFilesShownChanged(show); } bool DolphinView::hiddenFilesShown() const { return m_model->showHiddenFiles(); } void DolphinView::setGroupedSorting(bool grouped) { if (grouped == groupedSorting()) { return; } ViewProperties props(viewPropertiesUrl()); props.setGroupedSorting(grouped); props.save(); m_container->controller()->model()->setGroupedSorting(grouped); emit groupedSortingChanged(grouped); } bool DolphinView::groupedSorting() const { return m_model->groupedSorting(); } KFileItemList DolphinView::items() const { KFileItemList list; const int itemCount = m_model->count(); list.reserve(itemCount); for (int i = 0; i < itemCount; ++i) { list.append(m_model->fileItem(i)); } return list; } int DolphinView::itemsCount() const { return m_model->count(); } KFileItemList DolphinView::selectedItems() const { const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); KFileItemList selectedItems; const auto items = selectionManager->selectedItems(); selectedItems.reserve(items.count()); for (int index : items) { selectedItems.append(m_model->fileItem(index)); } return selectedItems; } int DolphinView::selectedItemsCount() const { const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); return selectionManager->selectedItems().count(); } void DolphinView::markUrlsAsSelected(const QList& urls) { m_selectedUrls = urls; } void DolphinView::markUrlAsCurrent(const QUrl &url) { m_currentItemUrl = url; m_scrollToCurrentItem = true; } void DolphinView::selectItems(const QRegExp& pattern, bool enabled) { const KItemListSelectionManager::SelectionMode mode = enabled ? KItemListSelectionManager::Select : KItemListSelectionManager::Deselect; KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); for (int index = 0; index < m_model->count(); index++) { const KFileItem item = m_model->fileItem(index); if (pattern.exactMatch(item.text())) { // 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) // for that. selectionManager->setSelected(index, 1, mode); } } } void DolphinView::setZoomLevel(int level) { const int oldZoomLevel = zoomLevel(); m_view->setZoomLevel(level); if (zoomLevel() != oldZoomLevel) { hideToolTip(); emit zoomLevelChanged(zoomLevel(), oldZoomLevel); } } int DolphinView::zoomLevel() const { return m_view->zoomLevel(); } void DolphinView::setSortRole(const QByteArray& role) { if (role != sortRole()) { updateSortRole(role); } } QByteArray DolphinView::sortRole() const { const KItemModelBase* model = m_container->controller()->model(); return model->sortRole(); } void DolphinView::setSortOrder(Qt::SortOrder order) { if (sortOrder() != order) { updateSortOrder(order); } } Qt::SortOrder DolphinView::sortOrder() const { return m_model->sortOrder(); } void DolphinView::setSortFoldersFirst(bool foldersFirst) { if (sortFoldersFirst() != foldersFirst) { updateSortFoldersFirst(foldersFirst); } } bool DolphinView::sortFoldersFirst() const { return m_model->sortDirectoriesFirst(); } void DolphinView::setVisibleRoles(const QList& roles) { const QList previousRoles = roles; ViewProperties props(viewPropertiesUrl()); props.setVisibleRoles(roles); m_visibleRoles = roles; m_view->setVisibleRoles(roles); emit visibleRolesChanged(m_visibleRoles, previousRoles); } QList DolphinView::visibleRoles() const { return m_visibleRoles; } void DolphinView::reload() { QByteArray viewState; QDataStream saveStream(&viewState, QIODevice::WriteOnly); saveState(saveStream); setUrl(url()); loadDirectory(url(), true); QDataStream restoreStream(viewState); restoreState(restoreStream); } void DolphinView::readSettings() { const int oldZoomLevel = m_view->zoomLevel(); GeneralSettings::self()->load(); m_view->readSettings(); applyViewProperties(); const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1; m_container->controller()->setAutoActivationDelay(delay); const int newZoomLevel = m_view->zoomLevel(); if (newZoomLevel != oldZoomLevel) { emit zoomLevelChanged(newZoomLevel, oldZoomLevel); } } void DolphinView::writeSettings() { GeneralSettings::self()->save(); m_view->writeSettings(); } void DolphinView::setNameFilter(const QString& nameFilter) { m_model->setNameFilter(nameFilter); } QString DolphinView::nameFilter() const { return m_model->nameFilter(); } void DolphinView::setMimeTypeFilters(const QStringList& filters) { return m_model->setMimeTypeFilters(filters); } QStringList DolphinView::mimeTypeFilters() const { return m_model->mimeTypeFilters(); } QString DolphinView::statusBarText() const { QString summary; QString foldersText; QString filesText; int folderCount = 0; int fileCount = 0; KIO::filesize_t totalFileSize = 0; if (m_container->controller()->selectionManager()->hasSelection()) { // Give a summary of the status of the selected files const KFileItemList list = selectedItems(); foreach (const KFileItem& item, list) { if (item.isDir()) { ++folderCount; } else { ++fileCount; totalFileSize += item.size(); } } if (folderCount + fileCount == 1) { // If only one item is selected, show info about it return 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); } } else { calculateItemCount(fileCount, folderCount, totalFileSize); foldersText = i18ncp("@info:status", "1 Folder", "%1 Folders", folderCount); filesText = i18ncp("@info:status", "1 File", "%1 Files", fileCount); } if (fileCount > 0 && folderCount > 0) { summary = i18nc("@info:status folders, files (size)", "%1, %2 (%3)", foldersText, filesText, KFormat().formatByteSize(totalFileSize)); } else if (fileCount > 0) { summary = i18nc("@info:status files (size)", "%1 (%2)", filesText, KFormat().formatByteSize(totalFileSize)); } else if (folderCount > 0) { summary = foldersText; } else { summary = i18nc("@info:status", "0 Folders, 0 Files"); } return summary; } QList DolphinView::versionControlActions(const KFileItemList& items) const { QList actions; if (items.isEmpty()) { const KFileItem item = m_model->rootItem(); if (!item.isNull()) { actions = m_versionControlObserver->actions(KFileItemList() << item); } } else { actions = m_versionControlObserver->actions(items); } return actions; } void DolphinView::setUrl(const QUrl& url) { if (url == m_url) { return; } clearSelection(); m_url = url; hideToolTip(); disconnect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); // It is important to clear the items from the model before // applying the view properties, otherwise expensive operations // might be done on the existing items although they get cleared // anyhow afterwards by loadDirectory(). m_model->clear(); applyViewProperties(); loadDirectory(url); emit urlChanged(url); } void DolphinView::selectAll() { KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); selectionManager->setSelected(0, m_model->count()); } void DolphinView::invertSelection() { KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); selectionManager->setSelected(0, m_model->count(), KItemListSelectionManager::Toggle); } void DolphinView::clearSelection() { m_selectedUrls.clear(); m_container->controller()->selectionManager()->clearSelection(); } void DolphinView::renameSelectedItems() { const KFileItemList items = selectedItems(); if (items.isEmpty()) { return; } if (items.count() == 1 && GeneralSettings::renameInline()) { const int index = m_model->index(items.first()); m_view->editRole(index, "text"); hideToolTip(); connect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); } else { RenameDialog* dialog = new RenameDialog(this, items); + + connect(dialog, &RenameDialog::renamingFinished, this, &DolphinView::slotRenameDialogRenamingFinished); + dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); dialog->raise(); dialog->activateWindow(); } // Assure that the current index remains visible when KFileItemModel // will notify the view about changed items (which might result in // a changed sorting). m_assureVisibleCurrentIndex = true; } void DolphinView::trashSelectedItems() { 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, QUrl(QStringLiteral("trash:/")), job); KJobWidgets::setWindow(job, this); connect(job, &KIO::Job::result, this, &DolphinView::slotTrashFileFinished); } } void DolphinView::deleteSelectedItems() { const QList list = simplifiedSelectedUrls(); KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(window()); if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::del(list); KJobWidgets::setWindow(job, this); connect(job, &KIO::Job::result, this, &DolphinView::slotDeleteFileFinished); } } void DolphinView::cutSelectedItems() { QMimeData* mimeData = selectionMimeData(); KIO::setClipboardDataCut(mimeData, true); QApplication::clipboard()->setMimeData(mimeData); } void DolphinView::copySelectedItems() { QMimeData* mimeData = selectionMimeData(); QApplication::clipboard()->setMimeData(mimeData); } void DolphinView::paste() { pasteToUrl(url()); } void DolphinView::pasteIntoFolder() { const KFileItemList items = selectedItems(); if ((items.count() == 1) && items.first().isDir()) { pasteToUrl(items.first().url()); } } void DolphinView::stopLoading() { m_model->cancelDirectoryLoading(); } void DolphinView::updatePalette() { QColor color = KColorScheme(QPalette::Active, 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(); } bool DolphinView::eventFilter(QObject* watched, QEvent* event) { switch (event->type()) { case QEvent::PaletteChange: updatePalette(); QPixmapCache::clear(); break; case QEvent::KeyPress: if (GeneralSettings::useTabForSwitchingSplitView()) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Tab && keyEvent->modifiers() == Qt::NoModifier) { emit toggleActiveViewRequested(); return true; } } break; case QEvent::FocusIn: if (watched == m_container) { setActive(true); } break; case QEvent::GraphicsSceneDragEnter: if (watched == m_view) { m_dragging = true; } break; case QEvent::GraphicsSceneDragLeave: if (watched == m_view) { m_dragging = false; } break; case QEvent::GraphicsSceneDrop: if (watched == m_view) { m_dragging = false; } default: break; } return QWidget::eventFilter(watched, event); } void DolphinView::wheelEvent(QWheelEvent* event) { if (event->modifiers().testFlag(Qt::ControlModifier)) { const int numDegrees = event->delta() / 8; const int numSteps = numDegrees / 15; setZoomLevel(zoomLevel() + numSteps); event->accept(); } else { event->ignore(); } } void DolphinView::hideEvent(QHideEvent* event) { hideToolTip(); QWidget::hideEvent(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) { hideToolTip(); } return QWidget::event(event); } void DolphinView::activate() { setActive(true); } void DolphinView::slotItemActivated(int index) { const KFileItem item = m_model->fileItem(index); if (!item.isNull()) { emit itemActivated(item); } } void DolphinView::slotItemsActivated(const KItemSet& indexes) { Q_ASSERT(indexes.count() >= 2); 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); if (answer != KMessageBox::Yes) { return; } } KFileItemList items; items.reserve(indexes.count()); for (int index : indexes) { KFileItem item = m_model->fileItem(index); const QUrl& url = openItemAsFolderUrl(item); if (!url.isEmpty()) { // Open folders in new tabs emit tabRequested(url); } else { items.append(item); } } if (items.count() == 1) { emit itemActivated(items.first()); } else if (items.count() > 1) { emit itemsActivated(items); } } void DolphinView::slotItemMiddleClicked(int index) { const KFileItem& item = m_model->fileItem(index); const QUrl& url = openItemAsFolderUrl(item); if (!url.isEmpty()) { emit tabRequested(url); } else if (isTabsForFilesEnabled()) { emit tabRequested(item.url()); } } void DolphinView::slotItemContextMenuRequested(int index, const QPointF& pos) { // Force emit of a selection changed signal before we request the // context menu, to update the edit-actions first. (See Bug 294013) if (m_selectionChangedTimer->isActive()) { emitSelectionChangedSignal(); } const KFileItem item = m_model->fileItem(index); emit requestContextMenu(pos.toPoint(), item, url(), QList()); } void DolphinView::slotViewContextMenuRequested(const QPointF& pos) { emit requestContextMenu(pos.toPoint(), KFileItem(), url(), QList()); } void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos) { ViewProperties props(viewPropertiesUrl()); QPointer menu = new QMenu(QApplication::activeWindow()); KItemListView* view = m_container->controller()->view(); const QSet visibleRolesSet = view->visibleRoles().toSet(); bool indexingEnabled = false; #ifdef HAVE_BALOO Baloo::IndexerConfig config; indexingEnabled = config.fileIndexingEnabled(); #endif QString groupName; QMenu* groupMenu = 0; // 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) { 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; if (info.group.isEmpty()) { action = menu->addAction(text); } else { if (!groupMenu || info.group != groupName) { groupName = info.group; groupMenu = menu->addMenu(groupName); } action = groupMenu->addAction(text); } action->setCheckable(true); action->setChecked(visibleRolesSet.contains(info.role)); action->setData(info.role); const bool enable = (!info.requiresBaloo && !info.requiresIndexer) || (info.requiresBaloo) || (info.requiresIndexer && indexingEnabled); action->setEnabled(enable); } menu->addSeparator(); QActionGroup* widthsGroup = new QActionGroup(menu); const bool autoColumnWidths = props.headerColumnWidths().isEmpty(); QAction* autoAdjustWidthsAction = menu->addAction(i18nc("@action:inmenu", "Automatic Column Widths")); autoAdjustWidthsAction->setCheckable(true); autoAdjustWidthsAction->setChecked(autoColumnWidths); autoAdjustWidthsAction->setActionGroup(widthsGroup); QAction* customWidthsAction = menu->addAction(i18nc("@action:inmenu", "Custom Column Widths")); customWidthsAction->setCheckable(true); customWidthsAction->setChecked(!autoColumnWidths); customWidthsAction->setActionGroup(widthsGroup); QAction* action = menu->exec(pos.toPoint()); if (menu && action) { KItemListHeader* header = view->header(); if (action == autoAdjustWidthsAction) { // Clear the column-widths from the viewproperties and turn on // the automatic resizing of the columns props.setHeaderColumnWidths(QList()); header->setAutomaticColumnResizing(true); } else if (action == customWidthsAction) { // Apply the current column-widths as custom column-widths and turn // off the automatic resizing of the columns QList columnWidths; columnWidths.reserve(view->visibleRoles().count()); foreach (const QByteArray& role, view->visibleRoles()) { columnWidths.append(header->columnWidth(role)); } props.setHeaderColumnWidths(columnWidths); header->setAutomaticColumnResizing(false); } else { // Show or hide the selected role const QByteArray selectedRole = action->data().toByteArray(); QList visibleRoles = view->visibleRoles(); if (action->isChecked()) { visibleRoles.append(selectedRole); } else { visibleRoles.removeOne(selectedRole); } view->setVisibleRoles(visibleRoles); props.setVisibleRoles(visibleRoles); QList columnWidths; if (!header->automaticColumnResizing()) { columnWidths.reserve(view->visibleRoles().count()); foreach (const QByteArray& role, view->visibleRoles()) { columnWidths.append(header->columnWidth(role)); } } props.setHeaderColumnWidths(columnWidths); } } delete menu; } void DolphinView::slotHeaderColumnWidthChangeFinished(const QByteArray& role, qreal current) { const QList visibleRoles = m_view->visibleRoles(); ViewProperties props(viewPropertiesUrl()); QList columnWidths = props.headerColumnWidths(); if (columnWidths.count() != visibleRoles.count()) { columnWidths.clear(); columnWidths.reserve(visibleRoles.count()); const KItemListHeader* header = m_view->header(); foreach (const QByteArray& role, visibleRoles) { const int width = header->columnWidth(role); columnWidths.append(width); } } const int roleIndex = visibleRoles.indexOf(role); Q_ASSERT(roleIndex >= 0 && roleIndex < columnWidths.count()); columnWidths[roleIndex] = current; props.setHeaderColumnWidths(columnWidths); } void DolphinView::slotItemHovered(int index) { const KFileItem item = m_model->fileItem(index); if (GeneralSettings::showToolTips() && !m_dragging) { QRectF itemRect = m_container->controller()->view()->itemContextRect(index); const QPoint pos = m_container->mapToGlobal(itemRect.topLeft().toPoint()); itemRect.moveTo(pos); m_toolTipManager->showToolTip(item, itemRect, nativeParentWidget()->windowHandle()); } emit requestItemInfo(item); } void DolphinView::slotItemUnhovered(int index) { Q_UNUSED(index); hideToolTip(); emit requestItemInfo(KFileItem()); } void DolphinView::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event) { 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 // or desktop-file destItem = m_model->rootItem(); destUrl = url(); } else { // The item represents a directory or desktop-file destUrl = destItem.mostLocalUrl(); } QDropEvent dropEvent(event->pos().toPoint(), event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); dropUrls(destUrl, &dropEvent, this); setActive(true); } void DolphinView::dropUrls(const QUrl &destUrl, QDropEvent *dropEvent, QWidget *dropWidget) { KIO::DropJob* job = DragAndDropHelper::dropUrls(destUrl, dropEvent, dropWidget); if (job) { connect(job, &KIO::DropJob::result, this, &DolphinView::slotPasteJobResult); 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) { Q_ASSERT(qobject_cast(previous)); KFileItemModel* fileItemModel = static_cast(previous); disconnect(fileItemModel, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); m_versionControlObserver->setModel(0); } if (current) { Q_ASSERT(qobject_cast(current)); KFileItemModel* fileItemModel = static_cast(current); connect(fileItemModel, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); m_versionControlObserver->setModel(fileItemModel); } } void DolphinView::slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons) { Q_UNUSED(itemIndex); hideToolTip(); if (buttons & Qt::BackButton) { emit goBackRequested(); } else if (buttons & Qt::ForwardButton) { emit goForwardRequested(); } } void DolphinView::slotItemCreated(const QUrl& url) { if (m_markFirstNewlySelectedItemAsCurrent) { markUrlAsCurrent(url); m_markFirstNewlySelectedItemAsCurrent = false; } m_selectedUrls << url; } void DolphinView::slotPasteJobResult(KJob *job) { if (job->error()) { emit errorMessage(job->errorString()); } if (!m_selectedUrls.isEmpty()) { m_selectedUrls << KDirModel::simplifiedUrlList(m_selectedUrls); } } void DolphinView::slotSelectionChanged(const KItemSet& current, const KItemSet& previous) { const int currentCount = current.count(); const int previousCount = previous.count(); const bool selectionStateChanged = (currentCount == 0 && previousCount > 0) || (currentCount > 0 && previousCount == 0); // If nothing has been selected before and something got selected (or if something // was selected before and now nothing is selected) the selectionChangedSignal must // be emitted asynchronously as fast as possible to update the edit-actions. m_selectionChangedTimer->setInterval(selectionStateChanged ? 0 : 300); m_selectionChangedTimer->start(); } void DolphinView::emitSelectionChangedSignal() { m_selectionChangedTimer->stop(); emit selectionChanged(selectedItems()); } void DolphinView::updateSortRole(const QByteArray& role) { ViewProperties props(viewPropertiesUrl()); props.setSortRole(role); KItemModelBase* model = m_container->controller()->model(); model->setSortRole(role); emit sortRoleChanged(role); } void DolphinView::updateSortOrder(Qt::SortOrder order) { ViewProperties props(viewPropertiesUrl()); props.setSortOrder(order); m_model->setSortOrder(order); emit sortOrderChanged(order); } void DolphinView::updateSortFoldersFirst(bool foldersFirst) { ViewProperties props(viewPropertiesUrl()); props.setSortFoldersFirst(foldersFirst); m_model->setSortDirectoriesFirst(foldersFirst); emit sortFoldersFirstChanged(foldersFirst); } QPair DolphinView::pasteInfo() const { const QMimeData *mimeData = QApplication::clipboard()->mimeData(); QPair info; info.second = KIO::pasteActionText(mimeData, &info.first, rootItem()); return info; } void DolphinView::setTabsForFilesEnabled(bool tabsForFiles) { m_tabsForFiles = tabsForFiles; } bool DolphinView::isTabsForFilesEnabled() const { return m_tabsForFiles; } bool DolphinView::itemsExpandable() const { return m_mode == DetailsView; } void DolphinView::restoreState(QDataStream& stream) { // Read the version number of the view state and check if the version is supported. quint32 version = 0; stream >> version; if (version != 1) { // The version of the view state isn't supported, we can't restore it. return; } // 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; stream >> urls; m_model->restoreExpandedDirectories(urls); } void DolphinView::saveState(QDataStream& stream) { stream << quint32(1); // View state version // Save the current item that has the keyboard focus const int currentIndex = m_container->controller()->selectionManager()->currentItem(); if (currentIndex != -1) { KFileItem item = m_model->fileItem(currentIndex); Q_ASSERT(!item.isNull()); // If the current index is valid a item must exist QUrl currentItemUrl = item.url(); stream << currentItemUrl; } else { 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(); stream << QPoint(x, y); // Save expanded folders (only relevant for the details view - the set will be empty in other view modes) stream << m_model->expandedDirectories(); } KFileItem DolphinView::rootItem() const { return m_model->rootItem(); } void DolphinView::setViewPropertiesContext(const QString& context) { m_viewPropertiesContext = context; } QString DolphinView::viewPropertiesContext() const { return m_viewPropertiesContext; } QUrl DolphinView::openItemAsFolderUrl(const KFileItem& item, const bool browseThroughArchives) { if (item.isNull()) { return QUrl(); } QUrl url = item.targetUrl(); if (item.isDir()) { return url; } if (item.isMimeTypeKnown()) { const QString& mimetype = item.mimetype(); if (browseThroughArchives && item.isFile() && url.isLocalFile()) { // Generic mechanism for redirecting to tar:// when clicking on a tar file, // zip:// when clicking on a zip file, etc. // The .protocol file specifies the mimetype that the kioslave handles. // Note that we don't use mimetype inheritance since we don't want to // open OpenDocument files as zip folders... const QString& protocol = KProtocolManager::protocolForArchiveMimetype(mimetype); if (!protocol.isEmpty()) { url.setScheme(protocol); return url; } } if (mimetype == QLatin1String("application/x-desktop")) { // Redirect to the URL in Type=Link desktop files, unless it is a http(s) URL. KDesktopFile desktopFile(url.toLocalFile()); if (desktopFile.hasLinkType()) { const QString linkUrl = desktopFile.readUrl(); if (!linkUrl.startsWith(QLatin1String("http"))) { return QUrl::fromUserInput(linkUrl); } } } } return QUrl(); } void DolphinView::observeCreatedItem(const QUrl& url) { if (m_active) { - clearSelection(); - markUrlAsCurrent(url); - markUrlsAsSelected({url}); + forceUrlsSelection(url, {url}); } } void DolphinView::slotDirectoryRedirection(const QUrl& oldUrl, const QUrl& newUrl) { if (oldUrl.matches(url(), QUrl::StripTrailingSlash)) { emit redirection(oldUrl, newUrl); m_url = newUrl; // #186947 } } void DolphinView::updateViewState() { 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; } } else { selectionManager->setCurrentItem(0); } m_currentItemUrl = QUrl(); } if (!m_restoredContentsPosition.isNull()) { const int x = m_restoredContentsPosition.x(); const int y = m_restoredContentsPosition.y(); m_restoredContentsPosition = QPoint(); m_container->horizontalScrollBar()->setValue(x); m_container->verticalScrollBar()->setValue(y); } if (!m_selectedUrls.isEmpty()) { KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); if (m_clearSelectionBeforeSelectingNewItems) { selectionManager->clearSelection(); m_clearSelectionBeforeSelectingNewItems = false; } 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; } } selectionManager->beginAnchoredSelection(selectionManager->currentItem()); selectionManager->setSelectedItems(selectedItems); } } void DolphinView::hideToolTip() { if (GeneralSettings::showToolTips()) { m_toolTipManager->hideToolTip(); } } void DolphinView::calculateItemCount(int& fileCount, int& folderCount, KIO::filesize_t& totalFileSize) const { 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(); } } } void DolphinView::slotTrashFileFinished(KJob* job) { if (job->error() == 0) { emit operationCompletedMessage(i18nc("@info:status", "Trash operation completed.")); } else if (job->error() != KIO::ERR_USER_CANCELED) { emit errorMessage(job->errorString()); } } void DolphinView::slotDeleteFileFinished(KJob* job) { if (job->error() == 0) { emit operationCompletedMessage(i18nc("@info:status", "Delete operation completed.")); } else if (job->error() != KIO::ERR_USER_CANCELED) { emit errorMessage(job->errorString()); } } void DolphinView::slotRenamingResult(KJob* job) { 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() { // Disable the writestate temporary until it can be determined in a fast way // in DolphinView::slotLoadingCompleted() if (m_isFolderWritable) { m_isFolderWritable = false; emit writeStateChanged(m_isFolderWritable); } emit directoryLoadingStarted(); } void DolphinView::slotDirectoryLoadingCompleted() { // 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, &DolphinView::updateViewState); emit directoryLoadingCompleted(); updateWritableState(); } void DolphinView::slotItemsChanged() { m_assureVisibleCurrentIndex = false; } void DolphinView::slotSortOrderChangedByHeader(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(previous); Q_ASSERT(m_model->sortOrder() == current); ViewProperties props(viewPropertiesUrl()); props.setSortOrder(current); emit sortOrderChanged(current); } void DolphinView::slotSortRoleChangedByHeader(const QByteArray& current, const QByteArray& previous) { Q_UNUSED(previous); Q_ASSERT(m_model->sortRole() == current); ViewProperties props(viewPropertiesUrl()); props.setSortRole(current); emit sortRoleChanged(current); } void DolphinView::slotVisibleRolesChangedByHeader(const QList& current, const QList& previous) { Q_UNUSED(previous); Q_ASSERT(m_container->controller()->view()->visibleRoles() == current); const QList previousVisibleRoles = m_visibleRoles; m_visibleRoles = current; ViewProperties props(viewPropertiesUrl()); props.setVisibleRoles(m_visibleRoles); emit visibleRolesChanged(m_visibleRoles, previousVisibleRoles); } void DolphinView::slotRoleEditingCanceled() { disconnect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); } void DolphinView::slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value) { disconnect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); if (index < 0 || index >= m_model->count()) { return; } 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 QUrl oldUrl = oldItem.url(); QUrl newUrl = oldUrl.adjusted(QUrl::RemoveFilename); newUrl.setPath(newUrl.path() + KIO::encodeFileName(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 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; data.insert(role, value); m_model->setData(index, data); } 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(job, &KJob::result, this, &DolphinView::slotRenamingResult); } } } } void DolphinView::loadDirectory(const QUrl& url, bool reload) { if (!url.isValid()) { const QString location(url.toDisplayString(QUrl::PreferLocalFile)); if (location.isEmpty()) { emit errorMessage(i18nc("@info:status", "The location is empty.")); } else { emit errorMessage(i18nc("@info:status", "The location '%1' is invalid.", location)); } return; } if (reload) { m_model->refreshDirectory(url); } else { m_model->loadDirectory(url); } } void DolphinView::applyViewProperties() { const ViewProperties props(viewPropertiesUrl()); applyViewProperties(props); } void DolphinView::applyViewProperties(const ViewProperties& props) { m_view->beginTransaction(); const Mode mode = props.viewMode(); if (m_mode != mode) { const Mode previousMode = m_mode; m_mode = mode; // Changing the mode might result in changing // the zoom level. Remember the old zoom level so // that zoomLevelChanged() can get emitted. const int oldZoomLevel = m_view->zoomLevel(); applyModeToView(); emit modeChanged(m_mode, previousMode); if (m_view->zoomLevel() != oldZoomLevel) { emit zoomLevelChanged(m_view->zoomLevel(), oldZoomLevel); } } const bool hiddenFilesShown = props.hiddenFilesShown(); if (hiddenFilesShown != m_model->showHiddenFiles()) { m_model->setShowHiddenFiles(hiddenFilesShown); emit hiddenFilesShownChanged(hiddenFilesShown); } const bool groupedSorting = props.groupedSorting(); if (groupedSorting != m_model->groupedSorting()) { m_model->setGroupedSorting(groupedSorting); emit groupedSortingChanged(groupedSorting); } const QByteArray sortRole = props.sortRole(); if (sortRole != m_model->sortRole()) { m_model->setSortRole(sortRole); emit sortRoleChanged(sortRole); } const Qt::SortOrder sortOrder = props.sortOrder(); if (sortOrder != m_model->sortOrder()) { m_model->setSortOrder(sortOrder); emit sortOrderChanged(sortOrder); } const bool sortFoldersFirst = props.sortFoldersFirst(); if (sortFoldersFirst != m_model->sortDirectoriesFirst()) { m_model->setSortDirectoriesFirst(sortFoldersFirst); emit sortFoldersFirstChanged(sortFoldersFirst); } const QList visibleRoles = props.visibleRoles(); if (visibleRoles != m_visibleRoles) { const QList previousVisibleRoles = m_visibleRoles; m_visibleRoles = visibleRoles; m_view->setVisibleRoles(visibleRoles); emit visibleRolesChanged(m_visibleRoles, previousVisibleRoles); } const bool previewsShown = props.previewsShown(); if (previewsShown != m_view->previewsShown()) { const int oldZoomLevel = zoomLevel(); m_view->setPreviewsShown(previewsShown); emit previewsShownChanged(previewsShown); // Changing the preview-state might result in a changed zoom-level if (oldZoomLevel != zoomLevel()) { emit zoomLevelChanged(zoomLevel(), oldZoomLevel); } } KItemListView* itemListView = m_container->controller()->view(); if (itemListView->isHeaderVisible()) { KItemListHeader* header = itemListView->header(); const QList headerColumnWidths = props.headerColumnWidths(); const int rolesCount = m_visibleRoles.count(); if (headerColumnWidths.count() == rolesCount) { header->setAutomaticColumnResizing(false); QHash columnWidths; for (int i = 0; i < rolesCount; ++i) { columnWidths.insert(m_visibleRoles[i], headerColumnWidths[i]); } header->setColumnWidths(columnWidths); } else { header->setAutomaticColumnResizing(true); } } m_view->endTransaction(); } void DolphinView::applyModeToView() { switch (m_mode) { case IconsView: m_view->setItemLayout(KFileItemListView::IconsLayout); break; case CompactView: m_view->setItemLayout(KFileItemListView::CompactLayout); break; case DetailsView: m_view->setItemLayout(KFileItemListView::DetailsLayout); break; default: Q_ASSERT(false); break; } } void DolphinView::pasteToUrl(const QUrl& url) { 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::slotPasteJobResult); } QList DolphinView::simplifiedSelectedUrls() const { QList urls; const KFileItemList items = selectedItems(); urls.reserve(items.count()); foreach (const KFileItem& item, items) { urls.append(item.url()); } if (itemsExpandable()) { // TODO: Check if we still need KDirModel for this in KDE 5.0 urls = KDirModel::simplifiedUrlList(urls); } return urls; } QMimeData* DolphinView::selectionMimeData() const { const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager(); const KItemSet selectedIndexes = selectionManager->selectedItems(); return m_model->createMimeData(selectedIndexes); } void DolphinView::updateWritableState() { const bool wasFolderWritable = m_isFolderWritable; m_isFolderWritable = false; KFileItem item = m_model->rootItem(); 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(url()); item.setDelayedMimeTypes(true); } KFileItemListProperties capabilities(KFileItemList() << item); m_isFolderWritable = capabilities.supportsWriting(); if (m_isFolderWritable != wasFolderWritable) { emit writeStateChanged(m_isFolderWritable); } } QUrl DolphinView::viewPropertiesUrl() const { if (m_viewPropertiesContext.isEmpty()) { return m_url; } 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); +} diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index 5c832efd1..911103b5d 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -1,807 +1,816 @@ /*************************************************************************** * 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 * ***************************************************************************/ #ifndef DOLPHINVIEW_H #define DOLPHINVIEW_H #include #include "dolphin_export.h" #include #include #include #include #include #include #include typedef KIO::FileUndoManager::CommandType CommandType; class QVBoxLayout; class DolphinItemListView; class KFileItemModel; class KItemListContainer; class KItemModelBase; class KItemSet; class ToolTipManager; class VersionControlObserver; class ViewProperties; class QGraphicsSceneDragDropEvent; class QRegExp; /** * @short Represents a view for the directory content. * * View modes for icons, compact and details are supported. It's * possible to adjust: * - sort order * - sort type * - show hidden files * - show previews * - enable grouping */ class DOLPHIN_EXPORT DolphinView : public QWidget { Q_OBJECT public: /** * Defines the view mode for a directory. The * view mode is automatically updated if the directory itself * defines a view mode (see class ViewProperties for details). */ enum Mode { /** * The items are shown as icons with a name-label below. */ IconsView = 0, /** * The icon, the name and the size of the items are * shown per default as a table. */ DetailsView, /** * The items are shown as icons with the name-label aligned * to the right side. */ CompactView }; /** * @param url Specifies the content which should be shown. * @param parent Parent widget of the view. */ DolphinView(const QUrl& url, QWidget* parent); virtual ~DolphinView(); /** * Returns the current active URL, where all actions are applied. * The URL navigator is synchronized with this URL. */ QUrl url() const; /** * If \a active is true, the view will marked as active. The active * view is defined as view where all actions are applied to. */ void setActive(bool active); bool isActive() const; /** * Changes the view mode for the current directory to \a mode. * If the view properties should be remembered for each directory * (GeneralSettings::globalViewProps() returns false), then the * changed view mode will be stored automatically. */ void setMode(Mode mode); Mode mode() const; /** * Turns on the file preview for the all files of the current directory, * if \a show is true. * If the view properties should be remembered for each directory * (GeneralSettings::globalViewProps() returns false), then the * preview setting will be stored automatically. */ void setPreviewsShown(bool show); bool previewsShown() const; /** * Shows all hidden files of the current directory, * if \a show is true. * If the view properties should be remembered for each directory * (GeneralSettings::globalViewProps() returns false), then the * show hidden file setting will be stored automatically. */ void setHiddenFilesShown(bool show); bool hiddenFilesShown() const; /** * Turns on sorting by groups if \a enable is true. */ void setGroupedSorting(bool grouped); bool groupedSorting() const; /** * Returns the items of the view. */ KFileItemList items() const; /** * @return The number of items. itemsCount() is faster in comparison * to items().count(). */ int itemsCount() const; /** * Returns the selected items. The list is empty if no item has been * selected. */ KFileItemList selectedItems() const; /** * Returns the number of selected items (this is faster than * invoking selectedItems().count()). */ int selectedItemsCount() const; /** * Marks the items indicated by \p urls to get selected after the * directory DolphinView::url() has been loaded. Note that nothing * gets selected if no loading of a directory has been triggered * by DolphinView::setUrl() or DolphinView::reload(). */ void markUrlsAsSelected(const QList &urls); /** * Marks the item indicated by \p url to be scrolled to and as the * current item after directory DolphinView::url() has been loaded. */ void markUrlAsCurrent(const QUrl& url); /** * All items that match to the pattern \a pattern will get selected * if \a enabled is true and deselected if \a enabled is false. */ void selectItems(const QRegExp& pattern, bool enabled); /** * Sets the zoom level to \a level. It is assured that the used * level is adjusted to be inside the range ZoomLevelInfo::minimumLevel() and * ZoomLevelInfo::maximumLevel(). */ void setZoomLevel(int level); int zoomLevel() const; void setSortRole(const QByteArray& role); QByteArray sortRole() const; void setSortOrder(Qt::SortOrder order); Qt::SortOrder sortOrder() const; /** Sets a separate sorting with folders first (true) or a mixed sorting of files and folders (false). */ void setSortFoldersFirst(bool foldersFirst); bool sortFoldersFirst() const; /** Sets the additional information which should be shown for the items. */ void setVisibleRoles(const QList& roles); /** Returns the additional information which should be shown for the items. */ QList visibleRoles() const; void reload(); /** * Refreshes the view to get synchronized with the settings (e.g. icons size, * font, ...). */ void readSettings(); /** * Saves the current settings (e.g. icons size, font, ..). */ void writeSettings(); /** * Filters the currently shown items by \a nameFilter. All items * which contain the given filter string will be shown. */ void setNameFilter(const QString& nameFilter); QString nameFilter() const; /** * Filters the currently shown items by \a filters. All items * whose content-type matches those given by the list of filters * will be shown. */ void setMimeTypeFilters(const QStringList& filters); QStringList mimeTypeFilters() const; /** * Returns a textual representation of the state of the current * folder or selected items, suitable for use in the status bar. */ QString statusBarText() const; /** * Returns the version control actions that are provided for the items \p items. * Usually the actions are presented in the context menu. */ QList versionControlActions(const KFileItemList& items) const; /** * Returns the state of the paste action: * first is whether the action should be enabled * second is the text for the action */ QPair pasteInfo() const; /** * If \a tabsForFiles is true, the signal tabRequested() will also * emitted also for files. Per default tabs for files is disabled * and hence the signal tabRequested() will only be emitted for * directories. */ void setTabsForFilesEnabled(bool tabsForFiles); bool isTabsForFilesEnabled() const; /** * Returns true if the current view allows folders to be expanded, * i.e. presents a hierarchical view to the user. */ bool itemsExpandable() const; /** * Restores the view state (current item, contents position, details view expansion state) */ void restoreState(QDataStream& stream); /** * Saves the view state (current item, contents position, details view expansion state) */ void saveState(QDataStream& stream); /** * Returns the root item which represents the current URL. */ KFileItem rootItem() const; /** * Sets a context that is used for remembering the view-properties. * Per default the context is empty and the path of the currently set URL * is used for remembering the view-properties. Setting a custom context * makes sense if specific types of URLs (e.g. search-URLs) should * share common view-properties. */ void setViewPropertiesContext(const QString& context); QString viewPropertiesContext() const; /** * Checks if the given \a item can be opened as folder (e.g. archives). * This function will also adjust the \a url (e.g. change the protocol). * @return a valid and adjusted url if the item can be opened as folder, * otherwise return an empty url. */ static QUrl openItemAsFolderUrl(const KFileItem& item, const bool browseThroughArchives = true); public slots: /** * Changes the directory to \a url. If the current directory is equal to * \a url, nothing will be done (use DolphinView::reload() instead). */ void setUrl(const QUrl& url); /** * Selects all items. * @see DolphinView::selectedItems() */ void selectAll(); /** * Inverts the current selection: selected items get unselected, * unselected items get selected. * @see DolphinView::selectedItems() */ void invertSelection(); void clearSelection(); /** * Triggers the renaming of the currently selected items, where * the user must input a new name for the items. */ void renameSelectedItems(); /** * Moves all selected items to the trash. */ void trashSelectedItems(); /** * Deletes all selected items. */ void deleteSelectedItems(); /** * Copies all selected items to the clipboard and marks * the items as cut. */ void cutSelectedItems(); /** Copies all selected items to the clipboard. */ void copySelectedItems(); /** Pastes the clipboard data to this view. */ void paste(); /** * Pastes the clipboard data into the currently selected * folder. If the current selection is not exactly one folder, no * paste operation is done. */ void pasteIntoFolder(); /** * Handles a drop of @p dropEvent onto widget @p dropWidget and destination @p destUrl */ void dropUrls(const QUrl &destUrl, QDropEvent *dropEvent, QWidget *dropWidget); void stopLoading(); /** Activates the view if the item list container gets focus. */ virtual bool eventFilter(QObject* watched, QEvent* event) Q_DECL_OVERRIDE; signals: /** * Is emitted if the view has been activated by e. g. a mouse click. */ void activated(); /** Is emitted if the URL of the view has been changed to \a url. */ void urlChanged(const QUrl& url); /** * Is emitted when clicking on an item with the left mouse button. */ 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); /** * Is emitted if items have been added or deleted. */ void itemCountChanged(); /** * Is emitted if a new tab should be opened for the URL \a url. */ void tabRequested(const QUrl& url); /** * Is emitted if the view mode (IconsView, DetailsView, * PreviewsView) has been changed. */ void modeChanged(DolphinView::Mode current, DolphinView::Mode previous); /** Is emitted if the 'show preview' property has been changed. */ void previewsShownChanged(bool shown); /** Is emitted if the 'show hidden files' property has been changed. */ void hiddenFilesShownChanged(bool shown); /** Is emitted if the 'grouped sorting' property has been changed. */ void groupedSortingChanged(bool groupedSorting); /** Is emitted if the sorting by name, size or date has been changed. */ void sortRoleChanged(const QByteArray& role); /** Is emitted if the sort order (ascending or descending) has been changed. */ void sortOrderChanged(Qt::SortOrder order); /** * Is emitted if the sorting of files and folders (separate with folders * first or mixed) has been changed. */ void sortFoldersFirstChanged(bool foldersFirst); /** Is emitted if the additional information shown for this view has been changed. */ void visibleRolesChanged(const QList& current, const QList& previous); /** Is emitted if the zoom level has been changed by zooming in or out. */ void zoomLevelChanged(int current, int previous); /** * Is emitted if information of an item is requested to be shown e. g. in the panel. * If item is null, no item information request is pending. */ void requestItemInfo(const KFileItem& item); /** * Is emitted whenever the selection has been changed. */ void selectionChanged(const KFileItemList& selection); /** * Is emitted if a context menu is requested for the item \a item, * which is part of \a url. If the item is null, the context menu * for the URL should be shown and the custom actions \a customActions * will be added. */ void requestContextMenu(const QPoint& pos, const KFileItem& item, const QUrl& url, const QList& customActions); /** * Is emitted if an information message with the content \a msg * should be shown. */ void infoMessage(const QString& msg); /** * Is emitted if an error message with the content \a msg * should be shown. */ void errorMessage(const QString& msg); /** * Is emitted if an "operation completed" message with the content \a msg * should be shown. */ void operationCompletedMessage(const QString& msg); /** * Is emitted after DolphinView::setUrl() has been invoked and * the current directory is loaded. If this signal is emitted, * it is assured that the view contains already the correct root * URL and property settings. */ void directoryLoadingStarted(); /** * Is emitted after the directory triggered by DolphinView::setUrl() * has been loaded. */ void directoryLoadingCompleted(); /** * Is emitted after the directory loading triggered by DolphinView::setUrl() * has been canceled. */ void directoryLoadingCanceled(); /** * Is emitted after DolphinView::setUrl() has been invoked and provides * the information how much percent of the current directory have been loaded. */ void directoryLoadingProgress(int percent); /** * Is emitted if the sorting is done asynchronously and provides the * progress information of the sorting. */ void directorySortingProgress(int percent); /** * Emitted when the file-item-model emits redirection. * Testcase: fish://localhost */ void redirection(const QUrl& oldUrl, const QUrl& newUrl); /** * Is emitted when the URL set by DolphinView::setUrl() represents a file. * In this case no signal errorMessage() will be emitted. */ void urlIsFileError(const QUrl& url); /** * Is emitted when the write state of the folder has been changed. The application * should disable all actions like "Create New..." that depend on the write * state. */ void writeStateChanged(bool isFolderWritable); /** * Is emitted if the URL should be changed to the previous URL of the * history (e.g. because the "back"-mousebutton has been pressed). */ void goBackRequested(); /** * Is emitted if the URL should be changed to the next URL of the * history (e.g. because the "next"-mousebutton has been pressed). */ void goForwardRequested(); /** * Is emitted when the user wants to move the focus to another view. */ void toggleActiveViewRequested(); /** * Is emitted when the user clicks a tag or a link * in the metadata widget of a tooltip. */ void urlActivated(const QUrl& url); protected: /** Changes the zoom level if Control is pressed during a wheel event. */ virtual void wheelEvent(QWheelEvent* event) Q_DECL_OVERRIDE; virtual void hideEvent(QHideEvent* event) Q_DECL_OVERRIDE; virtual bool event(QEvent* event) Q_DECL_OVERRIDE; private slots: /** * Marks the view as active (DolphinView:isActive() will return true) * and emits the 'activated' signal if it is not already active. */ void activate(); void slotItemActivated(int index); void slotItemsActivated(const KItemSet& indexes); void slotItemMiddleClicked(int index); void slotItemContextMenuRequested(int index, const QPointF& pos); void slotViewContextMenuRequested(const QPointF& pos); void slotHeaderContextMenuRequested(const QPointF& pos); void slotHeaderColumnWidthChangeFinished(const QByteArray& role, qreal current); void slotItemHovered(int index); void slotItemUnhovered(int index); void slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event); void slotModelChanged(KItemModelBase* current, KItemModelBase* previous); void slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons); + void slotRenameDialogRenamingFinished(const QList& urls); /* * Is called when new items get pasted or dropped. */ void slotItemCreated(const QUrl &url); /* * Is called after all pasted or dropped items have been copied to destination. */ void slotPasteJobResult(KJob *job); /** * Emits the signal \a selectionChanged() with a small delay. This is * because getting all file items for the selection can be an expensive * operation. Fast selection changes are collected in this case and * the signal is emitted only after no selection change has been done * within a small delay. */ void slotSelectionChanged(const KItemSet& current, const KItemSet& previous); /** * Is called by emitDelayedSelectionChangedSignal() and emits the * signal \a selectionChanged() with all selected file items as parameter. */ void emitSelectionChangedSignal(); /** * Updates the view properties of the current URL to the * sorting given by \a role. */ void updateSortRole(const QByteArray& role); /** * Updates the view properties of the current URL to the * sort order given by \a order. */ void updateSortOrder(Qt::SortOrder order); /** * Updates the view properties of the current URL to the * sorting of files and folders (separate with folders first or mixed) given by \a foldersFirst. */ void updateSortFoldersFirst(bool foldersFirst); /** * Indicates in the status bar that the delete operation * of the job \a job has been finished. */ void slotDeleteFileFinished(KJob* job); /** * Indicates in the status bar that the trash operation * of the job \a job has been finished. */ void slotTrashFileFinished(KJob* job); /** * Invoked when the rename job is done, for error handling. */ void slotRenamingResult(KJob* job); /** * Invoked when the file item model has started the loading * of the directory specified by DolphinView::url(). */ void slotDirectoryLoadingStarted(); /** * Invoked when the file item model indicates that the loading of a directory has * been completed. Assures that pasted items and renamed items get seleced. */ void slotDirectoryLoadingCompleted(); /** * Is invoked when items of KFileItemModel have been changed. */ void slotItemsChanged(); /** * Is invoked when the sort order has been changed by the user by clicking * on a header item. The view properties of the directory will get updated. */ void slotSortOrderChangedByHeader(Qt::SortOrder current, Qt::SortOrder previous); /** * Is invoked when the sort role has been changed by the user by clicking * on a header item. The view properties of the directory will get updated. */ void slotSortRoleChangedByHeader(const QByteArray& current, const QByteArray& previous); /** * Is invoked when the visible roles have been changed by the user by dragging * a header item. The view properties of the directory will get updated. */ void slotVisibleRolesChangedByHeader(const QList& current, const QList& previous); void slotRoleEditingCanceled(); void slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value); /** * Observes the item with the URL \a url. As soon as the directory * model indicates that the item is available, the item will * get selected and it is assured that the item stays visible. */ void observeCreatedItem(const QUrl &url); /** * Called when a redirection happens. * Testcase: fish://localhost */ void slotDirectoryRedirection(const QUrl& oldUrl, const QUrl& newUrl); /** * Applies the state that has been restored by restoreViewState() * to the view. */ void updateViewState(); void hideToolTip(); /** * Calculates the number of currently shown files into * \a fileCount and the number of folders into \a folderCount. * The size of all files is written into \a totalFileSize. * It is recommend using this method instead of asking the * directory lister or the model directly, as it takes * filtering and hierarchical previews into account. */ void calculateItemCount(int& fileCount, int& folderCount, KIO::filesize_t& totalFileSize) const; private: void loadDirectory(const QUrl& url, bool reload = false); /** * Applies the view properties which are defined by the current URL * to the DolphinView properties. The view properties are read from a * .directory file either in the current directory, or in the * share/apps/dolphin/view_properties/ subfolder of the user's .kde folder. */ void applyViewProperties(); /** * Applies the given view properties to the DolphinView. */ void applyViewProperties(const ViewProperties& props); /** * Applies the m_mode property to the corresponding * itemlayout-property of the KItemListView. */ void applyModeToView(); /** * Helper method for DolphinView::paste() and DolphinView::pasteIntoFolder(). * Pastes the clipboard data into the URL \a url. */ void pasteToUrl(const QUrl& url); /** * Returns a list of URLs for all selected items. The list is * simplified, so that when the URLs are part of different tree * levels, only the parent is returned. */ QList simplifiedSelectedUrls() const; /** * Returns the MIME data for all selected items. */ QMimeData* selectionMimeData() const; /** * Updates m_isFolderWritable dependent on whether the folder represented by * the current URL is writable. If the state has changed, the signal * writeableStateChanged() will be emitted. */ void updateWritableState(); /** * @return The current URL if no viewproperties-context is given (see * DolphinView::viewPropertiesContext(), otherwise the context * is returned. */ QUrl viewPropertiesUrl() const; + /** + * Clears the selection and updates current item and selection according to the parameters + * + * @param current URL to be set as current + * @param selected list of selected items + */ + void forceUrlsSelection(const QUrl& current, const QList& selected); + private: void updatePalette(); bool m_active; bool m_tabsForFiles; bool m_assureVisibleCurrentIndex; bool m_isFolderWritable; bool m_dragging; // True if a dragging is done. Required to be able to decide whether a // tooltip may be shown when hovering an item. QUrl m_url; QString m_viewPropertiesContext; Mode m_mode; QList m_visibleRoles; QVBoxLayout* m_topLayout; KFileItemModel* m_model; DolphinItemListView* m_view; KItemListContainer* m_container; ToolTipManager* m_toolTipManager; QTimer* m_selectionChangedTimer; QUrl m_currentItemUrl; // Used for making the view to remember the current URL after F5 bool m_scrollToCurrentItem; // Used for marking we need to scroll to current item or not QPoint m_restoredContentsPosition; QList m_selectedUrls; // Used for making the view to remember selections after F5 bool m_clearSelectionBeforeSelectingNewItems; bool m_markFirstNewlySelectedItemAsCurrent; VersionControlObserver* m_versionControlObserver; // For unit tests friend class TestBase; friend class DolphinDetailsViewTest; friend class DolphinPart; // Accesses m_model }; /// Allow using DolphinView::Mode in QVariant Q_DECLARE_METATYPE(DolphinView::Mode) #endif // DOLPHINVIEW_H diff --git a/src/views/dolphinviewactionhandler.cpp b/src/views/dolphinviewactionhandler.cpp index 447d46f53..d0b9e7dc1 100644 --- a/src/views/dolphinviewactionhandler.cpp +++ b/src/views/dolphinviewactionhandler.cpp @@ -1,595 +1,600 @@ /*************************************************************************** * Copyright (C) 2008 by David Faure * * Copyright (C) 2012 by Peter Penz * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "dolphinviewactionhandler.h" #include #include "settings/viewpropertiesdialog.h" #include "views/dolphinview.h" #include "views/zoomlevelinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "dolphindebug.h" #ifdef HAVE_BALOO #include #endif DolphinViewActionHandler::DolphinViewActionHandler(KActionCollection* collection, QObject* parent) : QObject(parent), m_actionCollection(collection), m_currentView(0), m_sortByActions(), m_visibleRoles() { Q_ASSERT(m_actionCollection); createActions(); } void DolphinViewActionHandler::setCurrentView(DolphinView* view) { Q_ASSERT(view); if (m_currentView) { disconnect(m_currentView, 0, this, 0); } m_currentView = view; connect(view, &DolphinView::modeChanged, this, &DolphinViewActionHandler::updateViewActions); connect(view, &DolphinView::previewsShownChanged, this, &DolphinViewActionHandler::slotPreviewsShownChanged); connect(view, &DolphinView::sortOrderChanged, this, &DolphinViewActionHandler::slotSortOrderChanged); connect(view, &DolphinView::sortFoldersFirstChanged, this, &DolphinViewActionHandler::slotSortFoldersFirstChanged); connect(view, &DolphinView::visibleRolesChanged, this, &DolphinViewActionHandler::slotVisibleRolesChanged); connect(view, &DolphinView::groupedSortingChanged, this, &DolphinViewActionHandler::slotGroupedSortingChanged); connect(view, &DolphinView::hiddenFilesShownChanged, this, &DolphinViewActionHandler::slotHiddenFilesShownChanged); connect(view, &DolphinView::sortRoleChanged, this, &DolphinViewActionHandler::slotSortRoleChanged); connect(view, &DolphinView::zoomLevelChanged, this, &DolphinViewActionHandler::slotZoomLevelChanged); connect(view, &DolphinView::writeStateChanged, this, &DolphinViewActionHandler::slotWriteStateChanged); } DolphinView* DolphinViewActionHandler::currentView() { return m_currentView; } void DolphinViewActionHandler::createActions() { // This action doesn't appear in the GUI, it's for the shortcut only. // KNewFileMenu takes care of the GUI stuff. QAction* newDirAction = m_actionCollection->addAction(QStringLiteral("create_dir")); newDirAction->setText(i18nc("@action", "Create Folder...")); m_actionCollection->setDefaultShortcut(newDirAction, Qt::Key_F10); newDirAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); newDirAction->setEnabled(false); // Will be enabled in slotWriteStateChanged(bool) if the current URL is writable connect(newDirAction, &QAction::triggered, this, &DolphinViewActionHandler::createDirectory); // File menu KStandardAction::renameFile(this, &DolphinViewActionHandler::slotRename, m_actionCollection); KStandardAction::moveToTrash(this, &DolphinViewActionHandler::slotTrashActivated, m_actionCollection); - KStandardAction::deleteFile(this, &DolphinViewActionHandler::slotDeleteItems, m_actionCollection); + auto deleteAction = KStandardAction::deleteFile(this, &DolphinViewActionHandler::slotDeleteItems, m_actionCollection); + auto deleteShortcuts = deleteAction->shortcuts(); + if (!deleteShortcuts.contains(Qt::SHIFT | Qt::Key_Delete)) { + deleteShortcuts.append(Qt::SHIFT | Qt::Key_Delete); + m_actionCollection->setDefaultShortcuts(deleteAction, deleteShortcuts); + } // This action is useful for being enabled when KStandardAction::MoveToTrash should be // disabled and KStandardAction::DeleteFile is enabled (e.g. non-local files), so that Key_Del // can be used for deleting the file (#76016). It needs to be a separate action // so that the Edit menu isn't affected. QAction* deleteWithTrashShortcut = m_actionCollection->addAction(QStringLiteral("delete_shortcut")); // The descriptive text is just for the shortcuts editor. deleteWithTrashShortcut->setText(i18nc("@action \"Move to Trash\" for non-local files, etc.", "Delete (using shortcut for Trash)")); m_actionCollection->setDefaultShortcut(deleteWithTrashShortcut, QKeySequence::Delete); deleteWithTrashShortcut->setEnabled(false); connect(deleteWithTrashShortcut, &QAction::triggered, this, &DolphinViewActionHandler::slotDeleteItems); QAction *propertiesAction = m_actionCollection->addAction( QStringLiteral("properties") ); // Well, it's the File menu in dolphinmainwindow and the Edit menu in dolphinpart... :) propertiesAction->setText( i18nc("@action:inmenu File", "Properties") ); propertiesAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); m_actionCollection->setDefaultShortcuts(propertiesAction, {Qt::ALT + Qt::Key_Return, Qt::ALT + Qt::Key_Enter}); connect(propertiesAction, &QAction::triggered, this, &DolphinViewActionHandler::slotProperties); // View menu KToggleAction* iconsAction = iconsModeAction(); KToggleAction* compactAction = compactModeAction(); KToggleAction* detailsAction = detailsModeAction(); KSelectAction* viewModeActions = m_actionCollection->add(QStringLiteral("view_mode")); viewModeActions->setText(i18nc("@action:intoolbar", "View Mode")); viewModeActions->addAction(iconsAction); viewModeActions->addAction(compactAction); viewModeActions->addAction(detailsAction); viewModeActions->setToolBarMode(KSelectAction::MenuMode); connect(viewModeActions, static_cast(&KSelectAction::triggered), this, &DolphinViewActionHandler::slotViewModeActionTriggered); KStandardAction::zoomIn(this, &DolphinViewActionHandler::zoomIn, m_actionCollection); KStandardAction::zoomOut(this, &DolphinViewActionHandler::zoomOut, m_actionCollection); KToggleAction* showPreview = m_actionCollection->add(QStringLiteral("show_preview")); showPreview->setText(i18nc("@action:intoolbar", "Preview")); showPreview->setToolTip(i18nc("@info", "Show preview of files and folders")); showPreview->setIcon(QIcon::fromTheme(QStringLiteral("view-preview"))); connect(showPreview, &KToggleAction::triggered, this, &DolphinViewActionHandler::togglePreview); KToggleAction* sortDescending = m_actionCollection->add(QStringLiteral("descending")); sortDescending->setText(i18nc("@action:inmenu Sort", "Descending")); connect(sortDescending, &KToggleAction::triggered, this, &DolphinViewActionHandler::toggleSortOrder); KToggleAction* sortFoldersFirst = m_actionCollection->add(QStringLiteral("folders_first")); sortFoldersFirst->setText(i18nc("@action:inmenu Sort", "Folders First")); connect(sortFoldersFirst, &KToggleAction::triggered, this, &DolphinViewActionHandler::toggleSortFoldersFirst); // View -> Sort By QActionGroup* sortByActionGroup = createFileItemRolesActionGroup(QStringLiteral("sort_by_")); KActionMenu* sortByActionMenu = m_actionCollection->add(QStringLiteral("sort")); sortByActionMenu->setText(i18nc("@action:inmenu View", "Sort By")); sortByActionMenu->setDelayed(false); foreach (QAction* action, sortByActionGroup->actions()) { sortByActionMenu->addAction(action); } sortByActionMenu->addSeparator(); sortByActionMenu->addAction(sortDescending); sortByActionMenu->addAction(sortFoldersFirst); // View -> Additional Information QActionGroup* visibleRolesGroup = createFileItemRolesActionGroup(QStringLiteral("show_")); KActionMenu* visibleRolesMenu = m_actionCollection->add(QStringLiteral("additional_info")); visibleRolesMenu->setText(i18nc("@action:inmenu View", "Additional Information")); visibleRolesMenu->setDelayed(false); foreach (QAction* action, visibleRolesGroup->actions()) { visibleRolesMenu->addAction(action); } KToggleAction* showInGroups = m_actionCollection->add(QStringLiteral("show_in_groups")); showInGroups->setIcon(QIcon::fromTheme(QStringLiteral("view-group"))); showInGroups->setText(i18nc("@action:inmenu View", "Show in Groups")); connect(showInGroups, &KToggleAction::triggered, this, &DolphinViewActionHandler::toggleGroupedSorting); KToggleAction* showHiddenFiles = m_actionCollection->add(QStringLiteral("show_hidden_files")); showHiddenFiles->setText(i18nc("@action:inmenu View", "Hidden Files")); showHiddenFiles->setToolTip(i18nc("@info", "Visibility of hidden files and folders")); m_actionCollection->setDefaultShortcuts(showHiddenFiles, {Qt::ALT + Qt::Key_Period, Qt::Key_F8}); connect(showHiddenFiles, &KToggleAction::triggered, this, &DolphinViewActionHandler::toggleShowHiddenFiles); QAction* adjustViewProps = m_actionCollection->addAction(QStringLiteral("view_properties")); adjustViewProps->setText(i18nc("@action:inmenu View", "Adjust View Properties...")); connect(adjustViewProps, &QAction::triggered, this, &DolphinViewActionHandler::slotAdjustViewProperties); } QActionGroup* DolphinViewActionHandler::createFileItemRolesActionGroup(const QString& groupPrefix) { const bool isSortGroup = (groupPrefix == QLatin1String("sort_by_")); Q_ASSERT(isSortGroup || (!isSortGroup && groupPrefix == QLatin1String("show_"))); QActionGroup* rolesActionGroup = new QActionGroup(m_actionCollection); rolesActionGroup->setExclusive(isSortGroup); if (isSortGroup) { connect(rolesActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotSortTriggered); } else { connect(rolesActionGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::toggleVisibleRole); } QString groupName; KActionMenu* groupMenu = 0; QActionGroup* groupMenuGroup = 0; bool indexingEnabled = false; #ifdef HAVE_BALOO Baloo::IndexerConfig config; indexingEnabled = config.fileIndexingEnabled(); #endif const QList rolesInfo = KFileItemModel::rolesInformation(); foreach (const KFileItemModel::RoleInfo& info, rolesInfo) { if (!isSortGroup && info.role == "text") { // It should not be possible to hide the "text" role continue; } KToggleAction* action = 0; const QString name = groupPrefix + info.role; if (info.group.isEmpty()) { action = m_actionCollection->add(name); action->setActionGroup(rolesActionGroup); } else { if (!groupMenu || info.group != groupName) { groupName = info.group; groupMenu = m_actionCollection->add(groupName); groupMenu->setText(groupName); groupMenu->setActionGroup(rolesActionGroup); groupMenuGroup = new QActionGroup(groupMenu); groupMenuGroup->setExclusive(isSortGroup); if (isSortGroup) { connect(groupMenuGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::slotSortTriggered); } else { connect(groupMenuGroup, &QActionGroup::triggered, this, &DolphinViewActionHandler::toggleVisibleRole); } } action = new KToggleAction(groupMenu); action->setActionGroup(groupMenuGroup); groupMenu->addAction(action); } action->setText(info.translation); action->setData(info.role); const bool enable = (!info.requiresBaloo && !info.requiresIndexer) || (info.requiresBaloo) || (info.requiresIndexer && indexingEnabled); action->setEnabled(enable); if (isSortGroup) { m_sortByActions.insert(info.role, action); } else { m_visibleRoles.insert(info.role, action); } } return rolesActionGroup; } void DolphinViewActionHandler::slotViewModeActionTriggered(QAction* action) { const DolphinView::Mode mode = action->data().value(); m_currentView->setMode(mode); QAction* viewModeMenu = m_actionCollection->action(QStringLiteral("view_mode")); viewModeMenu->setIcon(action->icon()); } void DolphinViewActionHandler::slotRename() { emit actionBeingHandled(); m_currentView->renameSelectedItems(); } void DolphinViewActionHandler::slotTrashActivated() { emit actionBeingHandled(); m_currentView->trashSelectedItems(); } void DolphinViewActionHandler::slotDeleteItems() { emit actionBeingHandled(); m_currentView->deleteSelectedItems(); } void DolphinViewActionHandler::togglePreview(bool show) { emit actionBeingHandled(); m_currentView->setPreviewsShown(show); } void DolphinViewActionHandler::slotPreviewsShownChanged(bool shown) { Q_UNUSED(shown); // It is not enough to update the 'Show Preview' action, also // the 'Zoom In' and 'Zoom Out' actions must be adapted. updateViewActions(); } QString DolphinViewActionHandler::currentViewModeActionName() const { switch (m_currentView->mode()) { case DolphinView::IconsView: return QStringLiteral("icons"); case DolphinView::DetailsView: return QStringLiteral("details"); case DolphinView::CompactView: return QStringLiteral("compact"); default: Q_ASSERT(false); break; } return QString(); // can't happen } KActionCollection* DolphinViewActionHandler::actionCollection() { return m_actionCollection; } void DolphinViewActionHandler::updateViewActions() { QAction* viewModeAction = m_actionCollection->action(currentViewModeActionName()); if (viewModeAction) { viewModeAction->setChecked(true); QAction* viewModeMenu = m_actionCollection->action(QStringLiteral("view_mode")); viewModeMenu->setIcon(viewModeAction->icon()); } QAction* showPreviewAction = m_actionCollection->action(QStringLiteral("show_preview")); showPreviewAction->setChecked(m_currentView->previewsShown()); slotSortOrderChanged(m_currentView->sortOrder()); slotSortFoldersFirstChanged(m_currentView->sortFoldersFirst()); slotVisibleRolesChanged(m_currentView->visibleRoles(), QList()); slotGroupedSortingChanged(m_currentView->groupedSorting()); slotSortRoleChanged(m_currentView->sortRole()); slotZoomLevelChanged(m_currentView->zoomLevel(), -1); // Updates the "show_hidden_files" action state and icon slotHiddenFilesShownChanged(m_currentView->hiddenFilesShown()); } void DolphinViewActionHandler::zoomIn() { const int level = m_currentView->zoomLevel(); m_currentView->setZoomLevel(level + 1); updateViewActions(); } void DolphinViewActionHandler::zoomOut() { const int level = m_currentView->zoomLevel(); m_currentView->setZoomLevel(level - 1); updateViewActions(); } void DolphinViewActionHandler::toggleSortOrder() { const Qt::SortOrder order = (m_currentView->sortOrder() == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder; m_currentView->setSortOrder(order); } void DolphinViewActionHandler::toggleSortFoldersFirst() { const bool sortFirst = m_currentView->sortFoldersFirst(); m_currentView->setSortFoldersFirst(!sortFirst); } void DolphinViewActionHandler::slotSortOrderChanged(Qt::SortOrder order) { QAction* descending = m_actionCollection->action(QStringLiteral("descending")); const bool sortDescending = (order == Qt::DescendingOrder); descending->setChecked(sortDescending); } void DolphinViewActionHandler::slotSortFoldersFirstChanged(bool foldersFirst) { m_actionCollection->action(QStringLiteral("folders_first"))->setChecked(foldersFirst); } void DolphinViewActionHandler::toggleVisibleRole(QAction* action) { emit actionBeingHandled(); const QByteArray toggledRole = action->data().toByteArray(); QList roles = m_currentView->visibleRoles(); const bool show = action->isChecked(); const int index = roles.indexOf(toggledRole); const bool containsInfo = (index >= 0); if (show && !containsInfo) { roles.append(toggledRole); m_currentView->setVisibleRoles(roles); } else if (!show && containsInfo) { roles.removeAt(index); m_currentView->setVisibleRoles(roles); Q_ASSERT(roles.indexOf(toggledRole) < 0); } } void DolphinViewActionHandler::slotVisibleRolesChanged(const QList& current, const QList& previous) { Q_UNUSED(previous); const QSet checkedRoles = current.toSet(); QHashIterator it(m_visibleRoles); while (it.hasNext()) { it.next(); const QByteArray& role = it.key(); KToggleAction* action = it.value(); action->setChecked(checkedRoles.contains(role)); } } void DolphinViewActionHandler::toggleGroupedSorting(bool grouped) { m_currentView->setGroupedSorting(grouped); } void DolphinViewActionHandler::slotGroupedSortingChanged(bool groupedSorting) { QAction* showInGroupsAction = m_actionCollection->action(QStringLiteral("show_in_groups")); showInGroupsAction->setChecked(groupedSorting); } void DolphinViewActionHandler::toggleShowHiddenFiles(bool show) { emit actionBeingHandled(); m_currentView->setHiddenFilesShown(show); } void DolphinViewActionHandler::slotHiddenFilesShownChanged(bool shown) { QAction* showHiddenFilesAction = m_actionCollection->action(QStringLiteral("show_hidden_files")); showHiddenFilesAction->setChecked(shown); // #374508: don't overwrite custom icons. const QString iconName = showHiddenFilesAction->icon().name(); if (!iconName.isEmpty() && iconName != QLatin1String("visibility") && iconName != QLatin1String("hint")) { return; } showHiddenFilesAction->setIcon(QIcon::fromTheme(shown ? QStringLiteral("visibility") : QStringLiteral("hint"))); } void DolphinViewActionHandler::slotWriteStateChanged(bool isFolderWritable) { m_actionCollection->action(QStringLiteral("create_dir"))->setEnabled(isFolderWritable && KProtocolManager::supportsMakeDir(currentView()->url())); } KToggleAction* DolphinViewActionHandler::iconsModeAction() { KToggleAction* iconsView = m_actionCollection->add(QStringLiteral("icons")); iconsView->setText(i18nc("@action:inmenu View Mode", "Icons")); iconsView->setToolTip(i18nc("@info", "Icons view mode")); m_actionCollection->setDefaultShortcut(iconsView, Qt::CTRL | Qt::Key_1); iconsView->setIcon(QIcon::fromTheme(QStringLiteral("view-list-icons"))); iconsView->setData(QVariant::fromValue(DolphinView::IconsView)); return iconsView; } KToggleAction* DolphinViewActionHandler::compactModeAction() { KToggleAction* iconsView = m_actionCollection->add(QStringLiteral("compact")); iconsView->setText(i18nc("@action:inmenu View Mode", "Compact")); iconsView->setToolTip(i18nc("@info", "Compact view mode")); m_actionCollection->setDefaultShortcut(iconsView, Qt::CTRL | Qt::Key_2); iconsView->setIcon(QIcon::fromTheme(QStringLiteral("view-list-details"))); // TODO: discuss with Oxygen-team the wrong (?) name iconsView->setData(QVariant::fromValue(DolphinView::CompactView)); return iconsView; } KToggleAction* DolphinViewActionHandler::detailsModeAction() { KToggleAction* detailsView = m_actionCollection->add(QStringLiteral("details")); detailsView->setText(i18nc("@action:inmenu View Mode", "Details")); detailsView->setToolTip(i18nc("@info", "Details view mode")); m_actionCollection->setDefaultShortcut(detailsView, Qt::CTRL | Qt::Key_3); detailsView->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree"))); detailsView->setData(QVariant::fromValue(DolphinView::DetailsView)); return detailsView; } void DolphinViewActionHandler::slotSortRoleChanged(const QByteArray& role) { KToggleAction* action = m_sortByActions.value(role); if (action) { action->setChecked(true); if (!action->icon().isNull()) { QAction* sortByMenu = m_actionCollection->action(QStringLiteral("sort")); sortByMenu->setIcon(action->icon()); } } } void DolphinViewActionHandler::slotZoomLevelChanged(int current, int previous) { Q_UNUSED(previous); QAction* zoomInAction = m_actionCollection->action(KStandardAction::name(KStandardAction::ZoomIn)); if (zoomInAction) { zoomInAction->setEnabled(current < ZoomLevelInfo::maximumLevel()); } QAction* zoomOutAction = m_actionCollection->action(KStandardAction::name(KStandardAction::ZoomOut)); if (zoomOutAction) { zoomOutAction->setEnabled(current > ZoomLevelInfo::minimumLevel()); } } void DolphinViewActionHandler::slotSortTriggered(QAction* action) { // The radiobuttons of the "Sort By"-menu are split between the main-menu // and several sub-menus. Because of this they don't have a common // action-group that assures an exclusive toggle-state between the main-menu // actions and the sub-menu-actions. If an action gets checked, it must // be assured that all other actions get unchecked. QAction* sortByMenu = m_actionCollection->action(QStringLiteral("sort")); foreach (QAction* groupAction, sortByMenu->menu()->actions()) { KActionMenu* actionMenu = qobject_cast(groupAction); if (actionMenu) { foreach (QAction* subAction, actionMenu->menu()->actions()) { subAction->setChecked(false); } } else if (groupAction->actionGroup()) { groupAction->setChecked(false); } } action->setChecked(true); // Apply the activated sort-role to the view const QByteArray role = action->data().toByteArray(); m_currentView->setSortRole(role); } void DolphinViewActionHandler::slotAdjustViewProperties() { emit actionBeingHandled(); QPointer dialog = new ViewPropertiesDialog(m_currentView); dialog->exec(); delete dialog; } void DolphinViewActionHandler::slotProperties() { KPropertiesDialog* dialog = 0; const KFileItemList list = m_currentView->selectedItems(); if (list.isEmpty()) { const QUrl url = m_currentView->url(); dialog = new KPropertiesDialog(url, m_currentView); } else { dialog = new KPropertiesDialog(list, m_currentView); } dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); dialog->raise(); dialog->activateWindow(); } diff --git a/src/views/renamedialog.cpp b/src/views/renamedialog.cpp index df4827b3a..6309bfbdf 100644 --- a/src/views/renamedialog.cpp +++ b/src/views/renamedialog.cpp @@ -1,246 +1,255 @@ /*************************************************************************** * Copyright (C) 2006-2010 by Peter Penz (peter.penz@gmx.at) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "renamedialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include RenameDialog::RenameDialog(QWidget *parent, const KFileItemList& items) : QDialog(parent), m_renameOneItem(false), m_newName(), m_lineEdit(0), m_items(items), m_allExtensionsDifferent(true), m_spinBox(0) { const QSize minSize = minimumSize(); setMinimumSize(QSize(320, minSize.height())); const int itemCount = items.count(); Q_ASSERT(itemCount >= 1); m_renameOneItem = (itemCount == 1); setWindowTitle(m_renameOneItem ? i18nc("@title:window", "Rename Item") : i18nc("@title:window", "Rename Items")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); m_okButton = buttonBox->button(QDialogButtonBox::Ok); m_okButton->setDefault(true); m_okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &RenameDialog::slotAccepted); connect(buttonBox, &QDialogButtonBox::rejected, this, &RenameDialog::reject); m_okButton->setDefault(true); KGuiItem::assign(m_okButton, KGuiItem(i18nc("@action:button", "&Rename"), QStringLiteral("dialog-ok-apply"))); QWidget* page = new QWidget(this); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); QVBoxLayout* topLayout = new QVBoxLayout(page); QLabel* editLabel = 0; if (m_renameOneItem) { m_newName = items.first().name(); editLabel = new QLabel(xi18nc("@label:textbox", "Rename the item %1 to:", m_newName), page); editLabel->setTextFormat(Qt::PlainText); } else { m_newName = i18nc("@info:status", "New name #"); editLabel = new QLabel(i18ncp("@label:textbox", "Rename the %1 selected item to:", "Rename the %1 selected items to:", itemCount), page); } m_lineEdit = new QLineEdit(page); mainLayout->addWidget(m_lineEdit); connect(m_lineEdit, &QLineEdit::textChanged, this, &RenameDialog::slotTextChanged); int selectionLength = m_newName.length(); if (m_renameOneItem) { const QString fileName = items.first().url().toDisplayString(); QMimeDatabase db; const QString extension = db.suffixForFileName(fileName.toLower()); // If the current item is a directory, select the whole file name. if ((extension.length() > 0) && !items.first().isDir()) { // Don't select the extension selectionLength -= extension.length() + 1; } } else { // Don't select the # character --selectionLength; } m_lineEdit->setText(m_newName); m_lineEdit->setSelection(0, selectionLength); topLayout->addWidget(editLabel); topLayout->addWidget(m_lineEdit); if (!m_renameOneItem) { QSet extensions; foreach (const KFileItem& item, m_items) { QMimeDatabase db; const QString extension = db.suffixForFileName(item.url().toDisplayString().toLower()); if (extensions.contains(extension)) { m_allExtensionsDifferent = false; break; } extensions.insert(extension); } QLabel* infoLabel = new QLabel(i18nc("@info", "# will be replaced by ascending numbers starting with:"), page); mainLayout->addWidget(infoLabel); m_spinBox = new QSpinBox(page); m_spinBox->setMaximum(10000); m_spinBox->setMinimum(0); m_spinBox->setSingleStep(1); m_spinBox->setValue(1); m_spinBox->setDisplayIntegerBase(10); QHBoxLayout* horizontalLayout = new QHBoxLayout(page); horizontalLayout->setMargin(0); horizontalLayout->addWidget(infoLabel); horizontalLayout->addWidget(m_spinBox); topLayout->addLayout(horizontalLayout); } } RenameDialog::~RenameDialog() { } void RenameDialog::renameItem(const KFileItem &item, const QString& newName) { const QUrl oldUrl = item.url(); QUrl newUrl = oldUrl.adjusted(QUrl::RemoveFilename); newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName)); QWidget* widget = parentWidget(); if (!widget) { widget = this; } KIO::Job * job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(job, widget); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); + + if (!job->error()) { + m_renamedItems << newUrl; + } + job->uiDelegate()->setAutoErrorHandlingEnabled(true); } void RenameDialog::slotAccepted() { m_newName = m_lineEdit->text(); if (m_renameOneItem) { Q_ASSERT(m_items.count() == 1); renameItem(m_items.first(), m_newName); } else { renameItems(); } accept(); } void RenameDialog::slotTextChanged(const QString& newName) { bool enable = !newName.isEmpty() && (newName != QLatin1String("..")) && (newName != QLatin1String(".")); if (enable && !m_renameOneItem) { const int count = newName.count(QLatin1Char('#')); if (count == 0) { // Renaming multiple files without '#' will only work if all extensions are different. enable = m_allExtensionsDifferent; } else { // Assure that the new name contains exactly one # (or a connected sequence of #'s) const int first = newName.indexOf(QLatin1Char('#')); const int last = newName.lastIndexOf(QLatin1Char('#')); enable = (last - first + 1 == count); } } m_okButton->setEnabled(enable); } void RenameDialog::showEvent(QShowEvent* event) { m_lineEdit->setFocus(); QDialog::showEvent(event); } void RenameDialog::renameItems() { // Iterate through all items and rename them... int index = m_spinBox->value(); foreach (const KFileItem& item, m_items) { QString newName = indexedName(m_newName, index, QLatin1Char('#')); ++index; const QUrl oldUrl = item.url(); QMimeDatabase db; const QString extension = db.suffixForFileName(oldUrl.path().toLower()); if (!extension.isEmpty()) { newName.append(QLatin1Char('.')); newName.append(extension); } if (oldUrl.fileName() != newName) { renameItem(item, newName); } } + + if (!m_items.empty()) { + emit renamingFinished(m_renamedItems); + } } QString RenameDialog::indexedName(const QString& name, int index, const QChar& indexPlaceHolder) { QString newName = name; QString indexString = QString::number(index); // Insert leading zeros if necessary const int minIndexLength = name.count(indexPlaceHolder); while (indexString.length() < minIndexLength) { indexString.prepend(QLatin1Char('0')); } // Replace the index placeholders by the indexString const int placeHolderStart = newName.indexOf(indexPlaceHolder); newName.replace(placeHolderStart, minIndexLength, indexString); return newName; } diff --git a/src/views/renamedialog.h b/src/views/renamedialog.h index 3964c0a5c..7ead0ca9f 100644 --- a/src/views/renamedialog.h +++ b/src/views/renamedialog.h @@ -1,74 +1,78 @@ /*************************************************************************** * Copyright (C) 2006-2010 by Peter Penz (peter.penz@gmx.at) * * * * 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 * ***************************************************************************/ #ifndef RENAMEDIALOG_H #define RENAMEDIALOG_H #include "dolphin_export.h" #include #include #include class QLineEdit; class QSpinBox; class QPushButton; /** * @brief Dialog for renaming a variable number of files. */ class DOLPHIN_EXPORT RenameDialog : public QDialog { Q_OBJECT public: explicit RenameDialog(QWidget* parent, const KFileItemList& items); virtual ~RenameDialog(); +signals: + void renamingFinished(const QList& urls); + private slots: void slotAccepted(); void slotTextChanged(const QString& newName); protected: void showEvent(QShowEvent* event) override; private: void renameItems(); void renameItem(const KFileItem &item, const QString& newName); /** * @return Returns the string \p name, where the characters represented by * \p indexPlaceHolder get replaced by the index \p index. * E. g. Calling indexedName("Test #.jpg", 12, '#') returns "Test 12.jpg". * A connected sequence of placeholders results in leading zeros: * indexedName("Test ####.jpg", 12, '#') returns "Test 0012.jpg". */ static QString indexedName(const QString& name, int index, const QChar& indexPlaceHolder); private: bool m_renameOneItem; + QList m_renamedItems; QString m_newName; QLineEdit* m_lineEdit; KFileItemList m_items; bool m_allExtensionsDifferent; QSpinBox* m_spinBox; QPushButton* m_okButton; }; #endif