X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/38c34eeca315c7be58e65d4d3fb72aaf7b866719..9df6ef77aadbd2c142bc666d3c998d9ece47f7fe:/src/dolphinmainwindow.cpp diff --git a/src/dolphinmainwindow.cpp b/src/dolphinmainwindow.cpp index 5d31a55dc..1f483309f 100644 --- a/src/dolphinmainwindow.cpp +++ b/src/dolphinmainwindow.cpp @@ -8,6 +8,7 @@ #include "dolphinmainwindow.h" +#include "admin/workerintegration.h" #include "dolphin_generalsettings.h" #include "dolphinbookmarkhandler.h" #include "dolphincontextmenu.h" @@ -25,8 +26,10 @@ #include "panels/folders/folderspanel.h" #include "panels/places/placespanel.h" #include "panels/terminal/terminalpanel.h" +#include "search/dolphinquery.h" #include "selectionmode/actiontexthelper.h" #include "settings/dolphinsettingsdialog.h" +#include "statusbar/diskspaceusagemenu.h" #include "statusbar/dolphinstatusbar.h" #include "views/dolphinnewfilemenuobserver.h" #include "views/dolphinremoteencoding.h" @@ -37,29 +40,27 @@ #include #include #include +#include #include #include +#include +#include #include #include #include -#include -#if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0) #include -#else -#include -#endif #include #include #include #include #include -#include #include #include +#include +#include #include #include #include -#include #include #include #include @@ -70,7 +71,6 @@ #include #include -#include #include #include @@ -87,6 +87,14 @@ #include #include #include +#include +#include + +#include + +#if HAVE_X11 +#include +#endif namespace { @@ -97,7 +105,7 @@ namespace const int CurrentDolphinVersion = 202; // The maximum number of entries in the back/forward popup menu const int MaxNumberOfNavigationentries = 12; -// The maximum number of "Activate Tab" shortcuts +// The maximum number of "Go to Tab" shortcuts const int MaxActivateTabShortcuts = 9; } @@ -110,12 +118,19 @@ DolphinMainWindow::DolphinMainWindow() , m_remoteEncoding(nullptr) , m_settingsDialog() , m_bookmarkHandler(nullptr) + , m_disabledActionNotifier(nullptr) , m_lastHandleUrlOpenJob(nullptr) , m_terminalPanel(nullptr) , m_placesPanel(nullptr) , m_tearDownFromPlacesRequested(false) , m_backAction(nullptr) , m_forwardAction(nullptr) + , m_splitViewAction(nullptr) + , m_splitViewMenuAction(nullptr) + , m_diskSpaceUsageMenu(nullptr) + , m_sessionSaveTimer(nullptr) + , m_sessionSaveWatcher(nullptr) + , m_sessionSaveScheduled(false) { Q_INIT_RESOURCE(dolphin); @@ -129,6 +144,10 @@ DolphinMainWindow::DolphinMainWindow() setStateConfigGroup("State"); +#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) + new KColorSchemeManager(this); // Sets a sensible color scheme which fixes unreadable icons and text on Windows. +#endif + connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::errorMessage, this, &DolphinMainWindow::showErrorMessage); KIO::FileUndoManager *undoManager = KIO::FileUndoManager::self(); @@ -161,14 +180,29 @@ DolphinMainWindow::DolphinMainWindow() m_actionHandler = new DolphinViewActionHandler(actionCollection(), m_actionTextHelper, this); connect(m_actionHandler, &DolphinViewActionHandler::actionBeingHandled, this, &DolphinMainWindow::clearStatusBar); connect(m_actionHandler, &DolphinViewActionHandler::createDirectoryTriggered, this, &DolphinMainWindow::createDirectory); + connect(m_actionHandler, &DolphinViewActionHandler::createFileTriggered, this, &DolphinMainWindow::createFile); connect(m_actionHandler, &DolphinViewActionHandler::selectionModeChangeTriggered, this, &DolphinMainWindow::slotSetSelectionMode); + QAction *newDirAction = actionCollection()->action(QStringLiteral("create_dir")); + Q_CHECK_PTR(newDirAction); + m_newFileMenu->setNewFolderShortcutAction(newDirAction); + + QAction *newFileAction = actionCollection()->action(QStringLiteral("create_file")); + Q_CHECK_PTR(newFileAction); + m_newFileMenu->setNewFileShortcutAction(newFileAction); + m_remoteEncoding = new DolphinRemoteEncoding(this, m_actionHandler); connect(this, &DolphinMainWindow::urlChanged, m_remoteEncoding, &DolphinRemoteEncoding::slotAboutToOpenUrl); + m_disabledActionNotifier = new DisabledActionNotifier(this); + connect(m_disabledActionNotifier, &DisabledActionNotifier::disabledActionTriggered, this, [this](const QAction *, QString reason) { + m_activeViewContainer->showMessage(reason, KMessageWidget::Warning); + }); + setupDockWidgets(); - setupGUI(Save | Create | ToolBar); + const bool usePhoneUi{KRuntimePlatform::runtimePlatform().contains(QLatin1String("phone"))}; + setupGUI(Save | Create | ToolBar, usePhoneUi ? QStringLiteral("dolphinuiforphones.rc") : QString() /* load the default dolphinui.rc file */); stateChanged(QStringLiteral("new_file")); QClipboard *clipboard = QApplication::clipboard(); @@ -179,6 +213,15 @@ DolphinMainWindow::DolphinMainWindow() if (firstRun) { menuBar()->setVisible(false); + + if (usePhoneUi) { + Q_ASSERT(qobject_cast(m_placesPanel->parent())); + m_placesPanel->parentWidget()->hide(); + auto settings = GeneralSettings::self(); + settings->setShowZoomSlider(false); // Zooming can be done with pinch gestures instead and we are short on horizontal space. + settings->setRenameInline(false); // This works around inline renaming currently not working well with virtual keyboards. + settings->save(); // Otherwise the RenameInline setting is not picked up for the first time Dolphin is used. + } } const bool showMenu = !menuBar()->isHidden(); @@ -195,6 +238,7 @@ DolphinMainWindow::DolphinMainWindow() } updateAllowedToolbarAreas(); + updateNavigatorsBackground(); // enable middle-click on back/forward/up to open in a new tab auto *middleClickEventFilter = new MiddleClickActionEventFilter(this); @@ -219,6 +263,13 @@ DolphinMainWindow::~DolphinMainWindow() { // This fixes a crash on Wayland when closing the mainwindow while another dialog is open. disconnect(QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &DolphinMainWindow::updatePasteAction); + + // This fixes a crash in dolphinmainwindowtest where the connection below fires even though the KMainWindow destructor of this object is already running. + Q_CHECK_PTR(qobject_cast(m_placesPanel->parent())); + disconnect(static_cast(m_placesPanel->parent()), + &DolphinDockWidget::visibilityChanged, + this, + &DolphinMainWindow::slotPlacesPanelVisibilityChanged); } QVector DolphinMainWindow::viewContainers() const @@ -265,6 +316,11 @@ bool DolphinMainWindow::isInformationPanelEnabled() const #endif } +bool DolphinMainWindow::isSplitViewEnabledInCurrentTab() const +{ + return m_tabWidget->currentTabPage()->splitViewEnabled(); +} + void DolphinMainWindow::openFiles(const QStringList &files, bool splitView) { openFiles(QUrl::fromStringList(files), splitView); @@ -276,8 +332,10 @@ void DolphinMainWindow::activateWindow(const QString &activationToken) if (KWindowSystem::isPlatformWayland()) { KWindowSystem::setCurrentXdgActivationToken(activationToken); - } else { + } else if (KWindowSystem::isPlatformX11()) { +#if HAVE_X11 KStartupInfo::setNewStartupId(window()->windowHandle(), activationToken.toUtf8()); +#endif } KWindowSystem::activateWindow(window()->windowHandle()); @@ -336,6 +394,10 @@ void DolphinMainWindow::changeUrl(const QUrl &url) updatePasteAction(); updateViewActions(); updateGoActions(); + m_diskSpaceUsageMenu->setUrl(url); + + // will signal used urls to activities manager, too + m_recentFiles->addUrl(url, QString(), "inode/directory"); Q_EMIT urlChanged(url); } @@ -347,9 +409,9 @@ void DolphinMainWindow::slotTerminalDirectoryChanged(const QUrl &url) m_tearDownFromPlacesRequested = false; } - m_activeViewContainer->setAutoGrabFocus(false); + m_activeViewContainer->setGrabFocusOnUrlChange(false); changeUrl(url); - m_activeViewContainer->setAutoGrabFocus(true); + m_activeViewContainer->setGrabFocusOnUrlChange(true); } void DolphinMainWindow::slotEditableStateChanged(bool editable) @@ -430,7 +492,7 @@ void DolphinMainWindow::addToPlaces() } if (url.isValid()) { QString icon; - if (m_activeViewContainer->isSearchModeEnabled()) { + if (isSearchUrl(url)) { icon = QStringLiteral("folder-saved-search-symbolic"); } else { icon = KIO::iconNameForUrl(url); @@ -439,9 +501,9 @@ void DolphinMainWindow::addToPlaces() } } -void DolphinMainWindow::openNewTab(const QUrl &url) +DolphinTabPage *DolphinMainWindow::openNewTab(const QUrl &url) { - m_tabWidget->openNewTab(url, QUrl()); + return m_tabWidget->openNewTab(url, QUrl()); } void DolphinMainWindow::openNewTabAndActivate(const QUrl &url) @@ -457,7 +519,7 @@ void DolphinMainWindow::openNewWindow(const QUrl &url) void DolphinMainWindow::slotSplitViewChanged() { m_tabWidget->currentTabPage()->setSplitViewEnabled(GeneralSettings::splitView(), WithAnimation); - updateSplitAction(); + updateSplitActions(); } void DolphinMainWindow::openInNewTab() @@ -497,26 +559,68 @@ void DolphinMainWindow::openInNewWindow() } } -void DolphinMainWindow::showTarget() +void DolphinMainWindow::openInSplitView(const QUrl &url) { - const auto link = m_activeViewContainer->view()->selectedItems().at(0); - const auto linkLocationDir = QFileInfo(link.localPath()).absoluteDir(); - auto linkDestination = link.linkDest(); - if (QFileInfo(linkDestination).isRelative()) { - linkDestination = linkLocationDir.filePath(linkDestination); + QUrl newSplitViewUrl = url; + + if (newSplitViewUrl.isEmpty()) { + const KFileItemList list = m_activeViewContainer->view()->selectedItems(); + if (list.count() == 1) { + const KFileItem &item = list.first(); + newSplitViewUrl = DolphinView::openItemAsFolderUrl(item); + } + } + + if (newSplitViewUrl.isEmpty()) { + return; } - if (QFileInfo::exists(linkDestination)) { - KIO::highlightInFileManager({QUrl::fromLocalFile(linkDestination).adjusted(QUrl::StripTrailingSlash)}); + + DolphinTabPage *tabPage = m_tabWidget->currentTabPage(); + if (tabPage->splitViewEnabled()) { + tabPage->switchActiveView(); + tabPage->activeViewContainer()->setUrl(newSplitViewUrl); } else { - m_activeViewContainer->showMessage(xi18nc("@info", "Could not access %1.", linkDestination), DolphinViewContainer::Warning); + tabPage->setSplitViewEnabled(true, WithAnimation, newSplitViewUrl); + updateViewActions(); + } +} + +void DolphinMainWindow::showTarget() +{ + const KFileItem link = m_activeViewContainer->view()->selectedItems().at(0); + const QUrl destinationUrl = link.url().resolved(QUrl(link.linkDest())); + + auto job = KIO::stat(destinationUrl, KIO::StatJob::SourceSide, KIO::StatNoDetails); + + connect(job, &KJob::finished, this, [this, destinationUrl](KJob *job) { + KIO::StatJob *statJob = static_cast(job); + + if (statJob->error()) { + m_activeViewContainer->showMessage(job->errorString(), KMessageWidget::Error); + } else { + KIO::highlightInFileManager({destinationUrl}); + } + }); +} + +bool DolphinMainWindow::event(QEvent *event) +{ + if (event->type() == QEvent::ShortcutOverride) { + const QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Space && m_activeViewContainer->view()->handleSpaceAsNormalKey()) { + event->accept(); + return true; + } } + + return KXmlGuiWindow::event(event); } void DolphinMainWindow::showEvent(QShowEvent *event) { KXmlGuiWindow::showEvent(event); - if (!event->spontaneous()) { + if (!event->spontaneous() && m_activeViewContainer) { m_activeViewContainer->view()->setFocus(); } } @@ -606,7 +710,7 @@ void DolphinMainWindow::closeEvent(QCloseEvent *event) QStringList(), i18n("Do not ask again"), &doNotAskAgainCheckboxResult, - KMessageBox::Dangerous); + KMessageBox::Notify | KMessageBox::Dangerous); if (doNotAskAgainCheckboxResult) { GeneralSettings::setConfirmClosingTerminalRunningProgram(false); @@ -626,18 +730,76 @@ void DolphinMainWindow::closeEvent(QCloseEvent *event) } } - if (GeneralSettings::rememberOpenedTabs()) { + if (m_sessionSaveTimer && (m_sessionSaveTimer->isActive() || m_sessionSaveWatcher->isRunning())) { + const bool sessionSaveTimerActive = m_sessionSaveTimer->isActive(); + + m_sessionSaveTimer->stop(); + m_sessionSaveWatcher->disconnect(); + m_sessionSaveWatcher->waitForFinished(); + + if (sessionSaveTimerActive || m_sessionSaveScheduled) { + slotSaveSession(); + } + } + + GeneralSettings::setVersion(CurrentDolphinVersion); + GeneralSettings::self()->save(); + + KXmlGuiWindow::closeEvent(event); +} + +void DolphinMainWindow::slotSaveSession() +{ + m_sessionSaveScheduled = false; + + if (m_sessionSaveWatcher->isRunning()) { + // The previous session is still being saved - schedule another save. + m_sessionSaveWatcher->disconnect(); + connect(m_sessionSaveWatcher, &QFutureWatcher::finished, this, &DolphinMainWindow::slotSaveSession, Qt::SingleShotConnection); + m_sessionSaveScheduled = true; + } else if (!m_sessionSaveTimer->isActive()) { + // No point in saving the session if the timer is running (since it will save the session again when it times out). KConfigGui::setSessionConfig(QStringLiteral("dolphin"), QStringLiteral("dolphin")); KConfig *config = KConfigGui::sessionConfig(); saveGlobalProperties(config); savePropertiesInternal(config, 1); - config->sync(); + KConfigGroup group = config->group(QStringLiteral("Number")); + group.writeEntry("NumberOfWindows", 1); // Makes session restore aware that there is a window to restore. + + auto future = QtConcurrent::run([config]() { + config->sync(); + }); + m_sessionSaveWatcher->setFuture(future); } +} - GeneralSettings::setVersion(CurrentDolphinVersion); - GeneralSettings::self()->save(); +void DolphinMainWindow::setSessionAutoSaveEnabled(bool enable) +{ + if (enable) { + if (!m_sessionSaveTimer) { + m_sessionSaveTimer = new QTimer(this); + m_sessionSaveWatcher = new QFutureWatcher(this); + m_sessionSaveTimer->setSingleShot(true); + m_sessionSaveTimer->setInterval(22000); - KXmlGuiWindow::closeEvent(event); + connect(m_sessionSaveTimer, &QTimer::timeout, this, &DolphinMainWindow::slotSaveSession); + } + + connect(m_tabWidget, &DolphinTabWidget::urlChanged, m_sessionSaveTimer, qOverload<>(&QTimer::start), Qt::UniqueConnection); + connect(m_tabWidget, &DolphinTabWidget::tabCountChanged, m_sessionSaveTimer, qOverload<>(&QTimer::start), Qt::UniqueConnection); + connect(m_tabWidget, &DolphinTabWidget::activeViewChanged, m_sessionSaveTimer, qOverload<>(&QTimer::start), Qt::UniqueConnection); + } else if (m_sessionSaveTimer) { + m_sessionSaveTimer->stop(); + m_sessionSaveWatcher->disconnect(); + m_sessionSaveScheduled = false; + + m_sessionSaveWatcher->waitForFinished(); + + m_sessionSaveTimer->deleteLater(); + m_sessionSaveWatcher->deleteLater(); + m_sessionSaveTimer = nullptr; + m_sessionSaveWatcher = nullptr; + } } void DolphinMainWindow::saveProperties(KConfigGroup &group) @@ -653,21 +815,28 @@ void DolphinMainWindow::readProperties(const KConfigGroup &group) void DolphinMainWindow::updateNewMenu() { m_newFileMenu->checkUpToDate(); -#if KIO_VERSION >= QT_VERSION_CHECK(5, 97, 0) m_newFileMenu->setWorkingDirectory(activeViewContainer()->url()); -#else - m_newFileMenu->setPopupFiles(QList() << activeViewContainer()->url()); -#endif } void DolphinMainWindow::createDirectory() { -#if KIO_VERSION >= QT_VERSION_CHECK(5, 97, 0) - m_newFileMenu->setWorkingDirectory(activeViewContainer()->url()); -#else - m_newFileMenu->setPopupFiles(QList() << activeViewContainer()->url()); -#endif - m_newFileMenu->createDirectory(); + // When creating directory, namejob is being run. In network folders, + // this job can take long time, so instead of starting multiple namejobs, + // just check if we are already running one. This prevents opening multiple + // dialogs. BUG:481401 + if (!m_newFileMenu->isCreateDirectoryRunning()) { + m_newFileMenu->setWorkingDirectory(activeViewContainer()->url()); + m_newFileMenu->createDirectory(); + } +} + +void DolphinMainWindow::createFile() +{ + // Use the same logic as in createDirectory() + if (!m_newFileMenu->isCreateFileRunning()) { + m_newFileMenu->setWorkingDirectory(activeViewContainer()->url()); + m_newFileMenu->createFile(); + } } void DolphinMainWindow::quit() @@ -677,7 +846,7 @@ void DolphinMainWindow::quit() void DolphinMainWindow::showErrorMessage(const QString &message) { - m_activeViewContainer->showMessage(message, DolphinViewContainer::Error); + m_activeViewContainer->showMessage(message, KMessageWidget::Error); } void DolphinMainWindow::slotUndoAvailable(bool available) @@ -730,13 +899,14 @@ void DolphinMainWindow::paste() void DolphinMainWindow::find() { - m_activeViewContainer->setSearchModeEnabled(true); + m_activeViewContainer->setSearchBarVisible(true); + m_activeViewContainer->setFocusToSearchBar(); } void DolphinMainWindow::updateSearchAction() { QAction *toggleSearchAction = actionCollection()->action(QStringLiteral("toggle_search")); - toggleSearchAction->setChecked(m_activeViewContainer->isSearchModeEnabled()); + toggleSearchAction->setChecked(m_activeViewContainer->isSearchBarVisible()); } void DolphinMainWindow::updatePasteAction() @@ -744,6 +914,10 @@ void DolphinMainWindow::updatePasteAction() QAction *pasteAction = actionCollection()->action(KStandardAction::name(KStandardAction::Paste)); QPair pasteInfo = m_activeViewContainer->view()->pasteInfo(); pasteAction->setEnabled(pasteInfo.first); + m_disabledActionNotifier->setDisabledReason(pasteAction, + m_activeViewContainer->rootItem().isWritable() + ? i18nc("@info", "Cannot paste: The clipboard is empty.") + : i18nc("@info", "Cannot paste: You do not have permission to write into this folder.")); pasteAction->setText(pasteInfo.second); } @@ -769,9 +943,13 @@ QAction *DolphinMainWindow::urlNavigatorHistoryAction(const KUrlNavigator *urlNa { const QUrl url = urlNavigator->locationUrl(historyIndex); - QString text = url.toDisplayString(QUrl::PreferLocalFile); + QString text; - if (!urlNavigator->showFullPath()) { + if (isSearchUrl(url)) { + text = Search::DolphinQuery(url, QUrl{}).title(); + } else if (urlNavigator->showFullPath()) { + text = url.toDisplayString(QUrl::PreferLocalFile); + } else { const KFilePlacesModel *placesModel = DolphinPlacesModelSingleton::instance().placesModel(); const QModelIndex closestIdx = placesModel->closestItem(url); @@ -801,10 +979,11 @@ void DolphinMainWindow::slotAboutToShowBackPopupMenu() { const KUrlNavigator *urlNavigator = m_activeViewContainer->urlNavigatorInternalWithHistory(); int entries = 0; - m_backAction->menu()->clear(); + QMenu *menu = m_backAction->popupMenu(); + menu->clear(); for (int i = urlNavigator->historyIndex() + 1; i < urlNavigator->historySize() && entries < MaxNumberOfNavigationentries; ++i, ++entries) { - QAction *action = urlNavigatorHistoryAction(urlNavigator, i, m_backAction->menu()); - m_backAction->menu()->addAction(action); + QAction *action = urlNavigatorHistoryAction(urlNavigator, i, menu); + menu->addAction(action); } } @@ -829,10 +1008,11 @@ void DolphinMainWindow::slotAboutToShowForwardPopupMenu() { const KUrlNavigator *urlNavigator = m_activeViewContainer->urlNavigatorInternalWithHistory(); int entries = 0; - m_forwardAction->menu()->clear(); + QMenu *menu = m_forwardAction->popupMenu(); + menu->clear(); for (int i = urlNavigator->historyIndex() - 1; i >= 0 && entries < MaxNumberOfNavigationentries; --i, ++entries) { - QAction *action = urlNavigatorHistoryAction(urlNavigator, i, m_forwardAction->menu()); - m_forwardAction->menu()->addAction(action); + QAction *action = urlNavigatorHistoryAction(urlNavigator, i, menu); + menu->addAction(action); } } @@ -875,12 +1055,29 @@ void DolphinMainWindow::invertSelection() void DolphinMainWindow::toggleSplitView() { - DolphinTabPage *tabPage = m_tabWidget->currentTabPage(); - tabPage->setSplitViewEnabled(!tabPage->splitViewEnabled(), WithAnimation); + QUrl newSplitViewUrl; + const KFileItemList list = m_activeViewContainer->view()->selectedItems(); + if (list.count() == 1) { + const KFileItem &item = list.first(); + newSplitViewUrl = DolphinView::openItemAsFolderUrl(item); + } + DolphinTabPage *tabPage = m_tabWidget->currentTabPage(); + tabPage->setSplitViewEnabled(!tabPage->splitViewEnabled(), WithAnimation, newSplitViewUrl); + m_tabWidget->updateTabName(m_tabWidget->indexOf(tabPage)); updateViewActions(); } +void DolphinMainWindow::popoutSplitView() +{ + DolphinTabPage *tabPage = m_tabWidget->currentTabPage(); + if (!tabPage->splitViewEnabled()) + return; + openNewWindow((GeneralSettings::closeActiveSplitView() ? tabPage->activeViewContainer() : tabPage->inactiveViewContainer())->url()); + tabPage->setSplitViewEnabled(false, WithAnimation); + updateSplitActions(); +} + void DolphinMainWindow::toggleSplitStash() { DolphinTabPage *tabPage = m_tabWidget->currentTabPage(); @@ -968,8 +1165,10 @@ void DolphinMainWindow::replaceLocation() // If the text field currently has focus and everything is selected, // pressing the keyboard shortcut returns the whole thing to breadcrumb mode + // and goes back to the view, just like how it was before this action was triggered the first time. if (navigator->isUrlEditable() && lineEdit->hasFocus() && lineEdit->selectedText() == lineEdit->text()) { navigator->setUrlEditable(false); + m_activeViewContainer->view()->setFocus(); } else { navigator->setUrlEditable(true); navigator->setFocus(); @@ -993,11 +1192,21 @@ void DolphinMainWindow::togglePanelLockState() GeneralSettings::setLockPanels(newLockState); } -void DolphinMainWindow::slotTerminalPanelVisibilityChanged() +void DolphinMainWindow::slotTerminalPanelVisibilityChanged(bool visible) { - if (m_terminalPanel->isHiddenInVisibleWindow() && m_activeViewContainer) { + if (!visible && m_activeViewContainer) { m_activeViewContainer->view()->setFocus(); } + // Putting focus to the Terminal is not handled here but in TerminalPanel::showEvent(). +} + +void DolphinMainWindow::slotPlacesPanelVisibilityChanged(bool visible) +{ + if (!visible && m_activeViewContainer) { + m_activeViewContainer->view()->setFocus(); + return; + } + m_placesPanel->setFocus(); } void DolphinMainWindow::goBack() @@ -1084,15 +1293,21 @@ void DolphinMainWindow::toggleShowMenuBar() QPointer DolphinMainWindow::preferredSearchTool() { m_searchTools.clear(); - KMoreToolsMenuFactory("dolphin/search-tools").fillMenuFromGroupingNames(&m_searchTools, {"files-find"}, m_activeViewContainer->url()); - QList actions = m_searchTools.actions(); - if (actions.isEmpty()) { - return nullptr; - } - QAction *action = actions.first(); - if (action->isSeparator()) { + + KService::Ptr kfind = KService::serviceByDesktopName(QStringLiteral("org.kde.kfind")); + + if (!kfind) { return nullptr; } + + auto *action = new QAction(QIcon::fromTheme(kfind->icon()), kfind->name(), this); + + connect(action, &QAction::triggered, this, [this, kfind] { + auto *job = new KIO::ApplicationLauncherJob(kfind); + job->setUrls({m_activeViewContainer->url()}); + job->start(); + }); + return action; } @@ -1136,7 +1351,8 @@ void DolphinMainWindow::openTerminalHere() { QList urls = {}; - for (const KFileItem &item : m_activeViewContainer->view()->selectedItems()) { + const auto selectedItems = m_activeViewContainer->view()->selectedItems(); + for (const KFileItem &item : selectedItems) { QUrl url = item.targetUrl(); if (item.isFile()) { url.setPath(QFileInfo(url.path()).absolutePath()); @@ -1154,29 +1370,19 @@ void DolphinMainWindow::openTerminalHere() if (urls.count() > 5) { QString question = i18np("Are you sure you want to open 1 terminal window?", "Are you sure you want to open %1 terminal windows?", urls.count()); -#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0) - const int answer = KMessageBox::warningTwoActions( - this, - question, - {}, -#else - const int answer = KMessageBox::warningYesNo( + const int answer = KMessageBox::warningContinueCancel( this, question, {}, -#endif KGuiItem(i18ncp("@action:button", "Open %1 Terminal", "Open %1 Terminals", urls.count()), QStringLiteral("utilities-terminal")), - KStandardGuiItem::cancel()); -#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0) - if (answer != KMessageBox::PrimaryAction) { -#else - if (answer != KMessageBox::Yes) { -#endif + KStandardGuiItem::cancel(), + QStringLiteral("ConfirmOpenManyTerminals")); + if (answer != KMessageBox::PrimaryAction && answer != KMessageBox::Continue) { return; } } - for (const QUrl &url : urls) { + for (const QUrl &url : std::as_const(urls)) { openTerminalJob(url); } } @@ -1241,11 +1447,7 @@ void DolphinMainWindow::handleUrl(const QUrl &url) activeViewContainer()->setUrl(url); } else { m_lastHandleUrlOpenJob = new KIO::OpenUrlJob(url); -#if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0) m_lastHandleUrlOpenJob->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this)); -#else - m_lastHandleUrlOpenJob->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this)); -#endif m_lastHandleUrlOpenJob->setShowOpenOrExecuteDialog(true); connect(m_lastHandleUrlOpenJob, &KIO::OpenUrlJob::mimeTypeFound, this, [this, url](const QString &mimetype) { @@ -1270,12 +1472,19 @@ void DolphinMainWindow::slotWriteStateChanged(bool isFolderWritable) // trash:/ is writable but we don't want to create new items in it. // TODO: remove the trash check once https://phabricator.kde.org/T8234 is implemented newFileMenu()->setEnabled(isFolderWritable && m_activeViewContainer->url().scheme() != QLatin1String("trash")); + // When the menu is disabled, actions in it are disabled later in the event loop, and we need to set the disabled reason after that. + QTimer::singleShot(0, this, [this]() { + m_disabledActionNotifier->setDisabledReason(actionCollection()->action(QStringLiteral("create_file")), + i18nc("@info", "Cannot create new file: You do not have permission to create items in this folder.")); + m_disabledActionNotifier->setDisabledReason(actionCollection()->action(QStringLiteral("create_dir")), + i18nc("@info", "Cannot create new folder: You do not have permission to create items in this folder.")); + }); } void DolphinMainWindow::openContextMenu(const QPoint &pos, const KFileItem &item, const KFileItemList &selectedItems, const QUrl &url) { QPointer contextMenu = new DolphinContextMenu(this, item, selectedItems, url, &m_fileItemActions); - contextMenu.data()->exec(pos); + contextMenu->exec(pos); // Delete the menu, unless it has been deleted in its own nested event loop already. if (contextMenu) { @@ -1354,14 +1563,17 @@ void DolphinMainWindow::updateHamburgerMenu() // and to change the more general UI. if (!toolBar()->isVisible() || (!toolbarActions.contains(ac->action(QStringLiteral("icons"))) && !toolbarActions.contains(ac->action(QStringLiteral("compact"))) - && !toolbarActions.contains(ac->action(QStringLiteral("details"))) && !toolbarActions.contains(ac->action(QStringLiteral("view_mode"))))) { + && !toolbarActions.contains(ac->action(QStringLiteral("details"))) && !toolbarActions.contains(ac->action(QStringLiteral("view_mode")))) + && !toolbarActions.contains(ac->action(QStringLiteral("view_settings")))) { menu->addAction(ac->action(QStringLiteral("view_mode"))); } - menu->addAction(ac->action(QStringLiteral("show_hidden_files"))); - menu->addAction(ac->action(QStringLiteral("sort"))); - menu->addAction(ac->action(QStringLiteral("additional_info"))); - if (!GeneralSettings::showStatusBar() || !GeneralSettings::showZoomSlider()) { - menu->addAction(ac->action(QStringLiteral("zoom"))); + if (!toolBar()->isVisible() || !toolbarActions.contains(ac->action(QStringLiteral("view_settings")))) { + menu->addAction(ac->action(QStringLiteral("show_hidden_files"))); + menu->addAction(ac->action(QStringLiteral("sort"))); + menu->addAction(ac->action(QStringLiteral("additional_info"))); + if (!GeneralSettings::showStatusBar() || !GeneralSettings::showZoomSlider()) { + menu->addAction(ac->action(QStringLiteral("zoom"))); + } } menu->addAction(ac->action(QStringLiteral("panels"))); @@ -1385,6 +1597,8 @@ void DolphinMainWindow::slotPlaceActivated(const QUrl &url) // We can end up here if the user clicked a device in the Places Panel // which had been unmounted earlier, see https://bugs.kde.org/show_bug.cgi?id=161385. reloadView(); + + m_activeViewContainer->view()->setFocus(); // We always want the focus on the view after activating a place. } else { view->disableUrlNavigatorSelectionRequests(); changeUrl(url); @@ -1405,9 +1619,6 @@ void DolphinMainWindow::activeViewChanged(DolphinViewContainer *viewContainer) m_activeViewContainer = viewContainer; if (oldViewContainer) { - const QAction *toggleSearchAction = actionCollection()->action(QStringLiteral("toggle_search")); - toggleSearchAction->disconnect(oldViewContainer); - // Disconnect all signals between the old view container (container, // view and url navigator) and main window. oldViewContainer->disconnect(this); @@ -1418,12 +1629,13 @@ void DolphinMainWindow::activeViewChanged(DolphinViewContainer *viewContainer) if (auto secondaryUrlNavigator = navigators->secondaryUrlNavigator()) { secondaryUrlNavigator->disconnect(this); } + oldViewContainer->disconnect(m_diskSpaceUsageMenu); // except the requestItemInfo so that on hover the information panel can still be updated connect(oldViewContainer->view(), &DolphinView::requestItemInfo, this, &DolphinMainWindow::requestItemInfo); // Disconnect other slots. - disconnect(nullptr, + disconnect(oldViewContainer, &DolphinViewContainer::selectionModeChanged, actionCollection()->action(QStringLiteral("toggle_selection_mode")), &QAction::setChecked); @@ -1439,6 +1651,13 @@ void DolphinMainWindow::activeViewChanged(DolphinViewContainer *viewContainer) updateViewActions(); updateGoActions(); updateSearchAction(); + connect(m_diskSpaceUsageMenu, + &DiskSpaceUsageMenu::showMessage, + viewContainer, + [viewContainer](const QString &message, KMessageWidget::MessageType messageType) { + viewContainer->showMessage(message, messageType); + }); + connect(m_diskSpaceUsageMenu, &DiskSpaceUsageMenu::showInstallationProgress, viewContainer, &DolphinViewContainer::showProgress); const QUrl url = viewContainer->url(); Q_EMIT urlChanged(url); @@ -1469,7 +1688,7 @@ void DolphinMainWindow::slotStorageTearDownFromPlacesRequested(const QString &mo setViewsToHomeIfMountPathOpen(mountPath); }); - if (m_terminalPanel && m_terminalPanel->currentWorkingDirectory().startsWith(mountPath)) { + if (m_terminalPanel && m_terminalPanel->currentWorkingDirectoryIsChildOf(mountPath)) { m_tearDownFromPlacesRequested = true; m_terminalPanel->goHome(); // m_placesPanel->proceedWithTearDown() will be called in slotTerminalDirectoryChanged @@ -1484,7 +1703,7 @@ void DolphinMainWindow::slotStorageTearDownExternallyRequested(const QString &mo setViewsToHomeIfMountPathOpen(mountPath); }); - if (m_terminalPanel && m_terminalPanel->currentWorkingDirectory().startsWith(mountPath)) { + if (m_terminalPanel && m_terminalPanel->currentWorkingDirectoryIsChildOf(mountPath)) { m_tearDownFromPlacesRequested = false; m_terminalPanel->goHome(); } @@ -1519,7 +1738,8 @@ void DolphinMainWindow::setupActions() auto hamburgerMenuAction = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection()); // setup 'File' menu - m_newFileMenu = new DolphinNewFileMenu(actionCollection(), this); + m_newFileMenu = new DolphinNewFileMenu(nullptr, nullptr, this); + actionCollection()->addAction(QStringLiteral("new_menu"), m_newFileMenu); QMenu *menu = m_newFileMenu->menu(); menu->setTitle(i18nc("@title:menu Create new folder, file, link, etc.", "Create New")); menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); @@ -1531,7 +1751,7 @@ void DolphinMainWindow::setupActions() newWindow->setToolTip(i18nc("@info", "Open a new Dolphin window")); newWindow->setWhatsThis(xi18nc("@info:whatsthis", "This opens a new " - "window just like this one with the current location and view." + "window just like this one with the current location." "You can drag and drop items between windows.")); newWindow->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); @@ -1540,10 +1760,10 @@ void DolphinMainWindow::setupActions() newTab->setText(i18nc("@action:inmenu File", "New Tab")); newTab->setWhatsThis(xi18nc("@info:whatsthis", "This opens a new " - "Tab with the current location and view." - "A tab is an additional view within this window. " + "Tab with the current location." + "Tabs allow you to quickly switch between multiple locations and views within this window. " "You can drag and drop items between tabs.")); - actionCollection()->setDefaultShortcuts(newTab, {Qt::CTRL | Qt::Key_T, Qt::CTRL | Qt::SHIFT | Qt::Key_N}); + actionCollection()->setDefaultShortcut(newTab, Qt::CTRL | Qt::Key_T); connect(newTab, &QAction::triggered, this, &DolphinMainWindow::openNewActivatedTab); QAction *addToPlaces = actionCollection()->addAction(QStringLiteral("add_to_places")); @@ -1556,10 +1776,11 @@ void DolphinMainWindow::setupActions() QAction *closeTab = KStandardAction::close(m_tabWidget, QOverload<>::of(&DolphinTabWidget::closeTab), actionCollection()); closeTab->setText(i18nc("@action:inmenu File", "Close Tab")); + closeTab->setToolTip(i18nc("@info", "Close Tab")); closeTab->setWhatsThis(i18nc("@info:whatsthis", "This closes the " - "currently viewed tab. If no more tabs are left this window " - "will close instead.")); + "currently viewed tab. If no more tabs are left, this closes " + "the whole window instead.")); QAction *quitAction = KStandardAction::quit(this, &DolphinMainWindow::quit, actionCollection()); quitAction->setWhatsThis(i18nc("@info:whatsthis quit", "This closes this window.")); @@ -1606,34 +1827,36 @@ void DolphinMainWindow::setupActions() + cutCopyPastePara); QAction *copyToOtherViewAction = actionCollection()->addAction(QStringLiteral("copy_to_inactive_split_view")); - copyToOtherViewAction->setText(i18nc("@action:inmenu", "Copy to Inactive Split View")); - m_actionTextHelper->registerTextWhenNothingIsSelected(copyToOtherViewAction, i18nc("@action:inmenu", "Copy to Inactive Split View…")); + copyToOtherViewAction->setText(i18nc("@action:inmenu", "Copy to Other View")); + m_actionTextHelper->registerTextWhenNothingIsSelected(copyToOtherViewAction, i18nc("@action:inmenu", "Copy to Other View…")); copyToOtherViewAction->setWhatsThis(xi18nc("@info:whatsthis Copy", "This copies the selected items from " - "the active view to the inactive split view.")); + "the view in focus to the other view. " + "(Only available while in Split View mode.)")); copyToOtherViewAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); - copyToOtherViewAction->setIconText(i18nc("@action:inmenu Edit", "Copy to Inactive Split View")); + copyToOtherViewAction->setIconText(i18nc("@action:inmenu Edit", "Copy to Other View")); actionCollection()->setDefaultShortcut(copyToOtherViewAction, Qt::SHIFT | Qt::Key_F5); connect(copyToOtherViewAction, &QAction::triggered, this, &DolphinMainWindow::copyToInactiveSplitView); QAction *moveToOtherViewAction = actionCollection()->addAction(QStringLiteral("move_to_inactive_split_view")); - moveToOtherViewAction->setText(i18nc("@action:inmenu", "Move to Inactive Split View")); - m_actionTextHelper->registerTextWhenNothingIsSelected(moveToOtherViewAction, i18nc("@action:inmenu", "Move to Inactive Split View…")); + moveToOtherViewAction->setText(i18nc("@action:inmenu", "Move to Other View")); + m_actionTextHelper->registerTextWhenNothingIsSelected(moveToOtherViewAction, i18nc("@action:inmenu", "Move to Other View…")); moveToOtherViewAction->setWhatsThis(xi18nc("@info:whatsthis Move", "This moves the selected items from " - "the active view to the inactive split view.")); + "the view in focus to the other view. " + "(Only available while in Split View mode.)")); moveToOtherViewAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-cut"))); - moveToOtherViewAction->setIconText(i18nc("@action:inmenu Edit", "Move to Inactive Split View")); + moveToOtherViewAction->setIconText(i18nc("@action:inmenu Edit", "Move to Other View")); actionCollection()->setDefaultShortcut(moveToOtherViewAction, Qt::SHIFT | Qt::Key_F6); connect(moveToOtherViewAction, &QAction::triggered, this, &DolphinMainWindow::moveToInactiveSplitView); QAction *showFilterBar = actionCollection()->addAction(QStringLiteral("show_filter_bar")); - showFilterBar->setText(i18nc("@action:inmenu Tools", "Filter...")); + showFilterBar->setText(i18nc("@action:inmenu Tools", "Filter…")); showFilterBar->setToolTip(i18nc("@info:tooltip", "Show Filter Bar")); showFilterBar->setWhatsThis(xi18nc("@info:whatsthis", "This opens the " "Filter Bar at the bottom of the window. " - "There you can enter a text to filter the files and folders currently displayed. " + "There you can enter text to filter the files and folders currently displayed. " "Only those that contain the text in their name will be kept in view.")); showFilterBar->setIcon(QIcon::fromTheme(QStringLiteral("view-filter"))); actionCollection()->setDefaultShortcuts(showFilterBar, {Qt::CTRL | Qt::Key_I, Qt::Key_Slash}); @@ -1652,15 +1875,13 @@ void DolphinMainWindow::setupActions() connect(toggleFilter, &QAction::triggered, this, &DolphinMainWindow::toggleFilterBar); QAction *searchAction = KStandardAction::find(this, &DolphinMainWindow::find, actionCollection()); - searchAction->setText(i18n("Search...")); + searchAction->setText(i18n("Search…")); searchAction->setToolTip(i18nc("@info:tooltip", "Search for files and folders")); searchAction->setWhatsThis(xi18nc("@info:whatsthis find", "This helps you " - "find files and folders by opening a find bar. " + "find files and folders by opening a search bar. " "There you can enter search terms and specify settings to find the " - "objects you are looking for.Use this help again on " - "the find bar so we can have a look at it while the settings are " - "explained.")); + "items you are looking for.")); // toggle_search acts as a copy of the main searchAction to be used mainly // in the toolbar, with no default shortcut attached, to avoid messing with @@ -1672,6 +1893,13 @@ void DolphinMainWindow::setupActions() toggleSearchAction->setToolTip(searchAction->toolTip()); toggleSearchAction->setWhatsThis(searchAction->whatsThis()); toggleSearchAction->setCheckable(true); + connect(toggleSearchAction, &QAction::triggered, this, [this](bool checked) { + if (checked) { + find(); + } else { + m_activeViewContainer->setSearchBarVisible(false); + } + }); QAction *toggleSelectionModeAction = actionCollection()->addAction(QStringLiteral("toggle_selection_mode")); // i18n: This action toggles a selection mode. @@ -1687,6 +1915,7 @@ void DolphinMainWindow::setupActions() "")); toggleSelectionModeAction->setIcon(QIcon::fromTheme(QStringLiteral("quickwizard"))); toggleSelectionModeAction->setCheckable(true); + actionCollection()->setDefaultShortcut(toggleSelectionModeAction, Qt::Key_Space); connect(toggleSelectionModeAction, &QAction::triggered, this, &DolphinMainWindow::toggleSelectionMode); // A special version of the toggleSelectionModeAction for the toolbar that also contains a menu @@ -1697,7 +1926,7 @@ void DolphinMainWindow::setupActions() toggleSelectionModeToolBarAction->setWhatsThis(toggleSelectionModeAction->whatsThis()); actionCollection()->addAction(QStringLiteral("toggle_selection_mode_tool_bar"), toggleSelectionModeToolBarAction); toggleSelectionModeToolBarAction->setCheckable(true); - toggleSelectionModeToolBarAction->setPopupMode(QToolButton::DelayedPopup); + toggleSelectionModeToolBarAction->setPopupMode(KToolBarPopupAction::DelayedPopup); connect(toggleSelectionModeToolBarAction, &QAction::triggered, toggleSelectionModeAction, &QAction::trigger); connect(toggleSelectionModeAction, &QAction::toggled, toggleSelectionModeToolBarAction, &QAction::setChecked); @@ -1710,7 +1939,7 @@ void DolphinMainWindow::setupActions() invertSelection->setText(i18nc("@action:inmenu Edit", "Invert Selection")); invertSelection->setWhatsThis(xi18nc("@info:whatsthis invert", "This selects all " - "objects that you have currently not selected instead.")); + "items that you have currently not selected instead.")); invertSelection->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-invert"))); actionCollection()->setDefaultShortcut(invertSelection, Qt::CTRL | Qt::SHIFT | Qt::Key_A); connect(invertSelection, &QAction::triggered, this, &DolphinMainWindow::invertSelection); @@ -1723,14 +1952,32 @@ void DolphinMainWindow::setupActions() // setup 'View' menu // (note that most of it is set up in DolphinViewActionHandler) - QAction *split = actionCollection()->addAction(QStringLiteral("split_view")); - split->setWhatsThis(xi18nc("@info:whatsthis find", - "This splits " - "the folder view below into two autonomous views.This " - "way you can see two locations at once and move items between them " - "quickly.Click this again afterwards to recombine the views.")); - actionCollection()->setDefaultShortcut(split, Qt::Key_F3); - connect(split, &QAction::triggered, this, &DolphinMainWindow::toggleSplitView); + Admin::WorkerIntegration::createActAsAdminAction(actionCollection(), this); + + m_splitViewAction = actionCollection()->add(QStringLiteral("split_view")); + m_splitViewMenuAction = actionCollection()->addAction(QStringLiteral("split_view_menu")); + + m_splitViewAction->setWhatsThis(xi18nc("@info:whatsthis split", + "This presents " + "a second view side-by-side with the current view, so you can see " + "the contents of two folders at once and easily move items between " + "them.The view that is not \"in focus\" will be dimmed. " + "Click this button again to close one of the views.")); + m_splitViewMenuAction->setWhatsThis(m_splitViewAction->whatsThis()); + + // only set it for the menu version + actionCollection()->setDefaultShortcut(m_splitViewMenuAction, Qt::Key_F3); + + connect(m_splitViewAction, &QAction::triggered, this, &DolphinMainWindow::toggleSplitView); + connect(m_splitViewMenuAction, &QAction::triggered, this, &DolphinMainWindow::toggleSplitView); + + QAction *popoutSplit = actionCollection()->addAction(QStringLiteral("popout_split_view")); + popoutSplit->setWhatsThis(xi18nc("@info:whatsthis", + "If the view has been split, this will pop the view in focus " + "out into a new window.")); + popoutSplit->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); + actionCollection()->setDefaultShortcut(popoutSplit, Qt::SHIFT | Qt::Key_F3); + connect(popoutSplit, &QAction::triggered, this, &DolphinMainWindow::popoutSplitView); QAction *stashSplit = actionCollection()->addAction(QStringLiteral("split_stash")); actionCollection()->setDefaultShortcut(stashSplit, Qt::CTRL | Qt::Key_S); @@ -1742,7 +1989,14 @@ void DolphinMainWindow::setupActions() stashSplit->setVisible(sessionInterface && sessionInterface->isServiceRegistered(QStringLiteral("org.kde.kio.StashNotifier"))); connect(stashSplit, &QAction::triggered, this, &DolphinMainWindow::toggleSplitStash); - KStandardAction::redisplay(this, &DolphinMainWindow::reloadView, actionCollection()); + QAction *redisplay = KStandardAction::redisplay(this, &DolphinMainWindow::reloadView, actionCollection()); + redisplay->setToolTip(i18nc("@info:tooltip", "Refresh view")); + redisplay->setWhatsThis(xi18nc("@info:whatsthis refresh", + "This refreshes " + "the folder view." + "If the contents of this folder have changed, refreshing will re-scan this folder " + "and show you a newly-updated view of the files and folders contained here." + "If the view is split, this refreshes the one that is currently in focus.")); QAction *stop = actionCollection()->addAction(QStringLiteral("stop")); stop->setText(i18nc("@action:inmenu View", "Stop")); @@ -1769,7 +2023,7 @@ void DolphinMainWindow::setupActions() replaceLocation->setWhatsThis(xi18nc("@info:whatsthis", "This switches to editing the location and selects it " "so you can quickly enter a different location.")); - actionCollection()->setDefaultShortcut(replaceLocation, Qt::CTRL | Qt::Key_L); + actionCollection()->setDefaultShortcuts(replaceLocation, {Qt::CTRL | Qt::Key_L, Qt::ALT | Qt::Key_D}); connect(replaceLocation, &QAction::triggered, this, &DolphinMainWindow::replaceLocation); // setup 'Go' menu @@ -1779,14 +2033,15 @@ void DolphinMainWindow::setupActions() m_backAction->setObjectName(backAction->objectName()); m_backAction->setShortcuts(backAction->shortcuts()); } - m_backAction->setPopupMode(QToolButton::DelayedPopup); + m_backAction->setPopupMode(KToolBarPopupAction::DelayedPopup); connect(m_backAction, &QAction::triggered, this, &DolphinMainWindow::goBack); - connect(m_backAction->menu(), &QMenu::aboutToShow, this, &DolphinMainWindow::slotAboutToShowBackPopupMenu); - connect(m_backAction->menu(), &QMenu::triggered, this, &DolphinMainWindow::slotGoBack); + connect(m_backAction->popupMenu(), &QMenu::aboutToShow, this, &DolphinMainWindow::slotAboutToShowBackPopupMenu); + connect(m_backAction->popupMenu(), &QMenu::triggered, this, &DolphinMainWindow::slotGoBack); actionCollection()->addAction(m_backAction->objectName(), m_backAction); auto backShortcuts = m_backAction->shortcuts(); - backShortcuts.append(QKeySequence(Qt::Key_Backspace)); + // Prepend this shortcut, to avoid being hidden by the two-slot UI (#371130) + backShortcuts.prepend(QKeySequence(Qt::Key_Backspace)); actionCollection()->setDefaultShortcuts(m_backAction, backShortcuts); DolphinRecentTabsMenu *recentTabsMenu = new DolphinRecentTabsMenu(this); @@ -1807,10 +2062,10 @@ void DolphinMainWindow::setupActions() undoAction->setWhatsThis(xi18nc("@info:whatsthis", "This undoes " "the last change you made to files or folders." - "Such changes include creating, renaming " + "Such changes include creating, renaming " "and moving them to a different location " - "or to the Trash. Changes that can't " - "be undone will ask for your confirmation.")); + "or to the Trash. Any changes that cannot be undone " + "will ask for your confirmation beforehand.")); undoAction->setEnabled(false); // undo should be disabled by default { @@ -1819,25 +2074,25 @@ void DolphinMainWindow::setupActions() m_forwardAction->setObjectName(forwardAction->objectName()); m_forwardAction->setShortcuts(forwardAction->shortcuts()); } - m_forwardAction->setPopupMode(QToolButton::DelayedPopup); + m_forwardAction->setPopupMode(KToolBarPopupAction::DelayedPopup); connect(m_forwardAction, &QAction::triggered, this, &DolphinMainWindow::goForward); - connect(m_forwardAction->menu(), &QMenu::aboutToShow, this, &DolphinMainWindow::slotAboutToShowForwardPopupMenu); - connect(m_forwardAction->menu(), &QMenu::triggered, this, &DolphinMainWindow::slotGoForward); + connect(m_forwardAction->popupMenu(), &QMenu::aboutToShow, this, &DolphinMainWindow::slotAboutToShowForwardPopupMenu); + connect(m_forwardAction->popupMenu(), &QMenu::triggered, this, &DolphinMainWindow::slotGoForward); actionCollection()->addAction(m_forwardAction->objectName(), m_forwardAction); actionCollection()->setDefaultShortcuts(m_forwardAction, m_forwardAction->shortcuts()); // enable middle-click to open in a new tab auto *middleClickEventFilter = new MiddleClickActionEventFilter(this); connect(middleClickEventFilter, &MiddleClickActionEventFilter::actionMiddleClicked, this, &DolphinMainWindow::slotBackForwardActionMiddleClicked); - m_backAction->menu()->installEventFilter(middleClickEventFilter); - m_forwardAction->menu()->installEventFilter(middleClickEventFilter); + m_backAction->popupMenu()->installEventFilter(middleClickEventFilter); + m_forwardAction->popupMenu()->installEventFilter(middleClickEventFilter); KStandardAction::up(this, &DolphinMainWindow::goUp, actionCollection()); QAction *homeAction = KStandardAction::home(this, &DolphinMainWindow::goHome, actionCollection()); homeAction->setWhatsThis(xi18nc("@info:whatsthis", "Go to your " "Home folder.Every user account " - "has their own Home that contains their data " - "including folders that contain personal application data.")); + "has their own Home that contains their personal files, " + "as well as hidden folders for their applications' data and configuration files.")); // setup 'Tools' menu QAction *compareFiles = actionCollection()->addAction(QStringLiteral("compare_files")); @@ -1846,6 +2101,12 @@ void DolphinMainWindow::setupActions() compareFiles->setEnabled(false); connect(compareFiles, &QAction::triggered, this, &DolphinMainWindow::compareFiles); + QAction *manageDiskSpaceUsage = actionCollection()->addAction(QStringLiteral("manage_disk_space")); + manageDiskSpaceUsage->setText(i18nc("@action:inmenu Tools", "Manage Disk Space Usage")); + manageDiskSpaceUsage->setIcon(QIcon::fromTheme(QStringLiteral("filelight"))); + m_diskSpaceUsageMenu = new DiskSpaceUsageMenu{this}; + manageDiskSpaceUsage->setMenu(m_diskSpaceUsageMenu); + QAction *openPreferredSearchTool = actionCollection()->addAction(QStringLiteral("open_preferred_search_tool")); openPreferredSearchTool->setText(i18nc("@action:inmenu Tools", "Open Preferred Search Tool")); openPreferredSearchTool->setWhatsThis(xi18nc("@info:whatsthis", @@ -1856,12 +2117,18 @@ void DolphinMainWindow::setupActions() connect(openPreferredSearchTool, &QAction::triggered, this, &DolphinMainWindow::openPreferredSearchTool); if (KAuthorized::authorize(QStringLiteral("shell_access"))) { + // Get icon of user default terminal emulator application + const KConfigGroup group(KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig), QStringLiteral("General")); + const QString terminalDesktopFilename = group.readEntry("TerminalService"); + // Use utilities-terminal icon from theme if readEntry() has failed + const QString terminalIcon = terminalDesktopFilename.isEmpty() ? "utilities-terminal" : KDesktopFile(terminalDesktopFilename).readIcon(); + QAction *openTerminal = actionCollection()->addAction(QStringLiteral("open_terminal")); openTerminal->setText(i18nc("@action:inmenu Tools", "Open Terminal")); openTerminal->setWhatsThis(xi18nc("@info:whatsthis", "This opens a terminal application for the viewed location." - "To learn more about terminals use the help in the terminal application.")); - openTerminal->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); + "To learn more about terminals use the help features in the terminal application.")); + openTerminal->setIcon(QIcon::fromTheme(terminalIcon)); actionCollection()->setDefaultShortcut(openTerminal, Qt::SHIFT | Qt::Key_F4); connect(openTerminal, &QAction::triggered, this, &DolphinMainWindow::openTerminal); @@ -1870,18 +2137,10 @@ void DolphinMainWindow::setupActions() openTerminalHere->setText(i18nc("@action:inmenu Tools", "Open Terminal Here")); openTerminalHere->setWhatsThis(xi18nc("@info:whatsthis", "This opens terminal applications for the selected items' locations." - "To learn more about terminals use the help in the terminal application.")); - openTerminalHere->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); + "To learn more about terminals use the help features in the terminal application.")); + openTerminalHere->setIcon(QIcon::fromTheme(terminalIcon)); actionCollection()->setDefaultShortcut(openTerminalHere, Qt::SHIFT | Qt::ALT | Qt::Key_F4); connect(openTerminalHere, &QAction::triggered, this, &DolphinMainWindow::openTerminalHere); - -#if HAVE_TERMINAL - QAction *focusTerminalPanel = actionCollection()->addAction(QStringLiteral("focus_terminal_panel")); - focusTerminalPanel->setText(i18nc("@action:inmenu Tools", "Focus Terminal Panel")); - focusTerminalPanel->setIcon(QIcon::fromTheme(QStringLiteral("swap-panels"))); - actionCollection()->setDefaultShortcut(focusTerminalPanel, Qt::CTRL | Qt::SHIFT | Qt::Key_F4); - connect(focusTerminalPanel, &QAction::triggered, this, &DolphinMainWindow::focusTerminalPanel); -#endif } // setup 'Bookmarks' menu @@ -1896,10 +2155,10 @@ void DolphinMainWindow::setupActions() KToggleAction *showMenuBar = KStandardAction::showMenubar(nullptr, nullptr, actionCollection()); showMenuBar->setWhatsThis(xi18nc("@info:whatsthis", "This switches between having a Menubar " - "and having a %1 button. Both " + "and having an %1 button. Both " "contain mostly the same actions and configuration options." - "The Menubar takes up more space but allows for fast and organised access to all " - "actions an application has to offer.The %1 button " + "The Menubar takes up more space but allows for fast and organized access to all " + "actions an application has to offer.The %1 button " "is simpler and small which makes triggering advanced actions more time consuming.", hamburgerMenuAction->text().replace('&', ""))); connect(showMenuBar, @@ -1908,14 +2167,6 @@ void DolphinMainWindow::setupActions() &DolphinMainWindow::toggleShowMenuBar, Qt::QueuedConnection); - KToggleAction *showStatusBar = KStandardAction::showStatusbar(nullptr, nullptr, actionCollection()); - showStatusBar->setChecked(GeneralSettings::showStatusBar()); - connect(GeneralSettings::self(), &GeneralSettings::showStatusBarChanged, showStatusBar, &KToggleAction::setChecked); - connect(showStatusBar, &KToggleAction::triggered, this, [this](bool checked) { - GeneralSettings::setShowStatusBar(checked); - refreshViews(); - }); - KStandardAction::keyBindings(this, &DolphinMainWindow::slotKeyBindings, actionCollection()); KStandardAction::preferences(this, &DolphinMainWindow::editSettings, actionCollection()); @@ -1928,7 +2179,7 @@ void DolphinMainWindow::setupActions() for (int i = 0; i < MaxActivateTabShortcuts; ++i) { QAction *activateTab = actionCollection()->addAction(QStringLiteral("activate_tab_%1").arg(i)); - activateTab->setText(i18nc("@action:inmenu", "Activate Tab %1", i + 1)); + activateTab->setText(i18nc("@action:inmenu", "Go to Tab %1", i + 1)); activateTab->setEnabled(false); connect(activateTab, &QAction::triggered, this, [this, i]() { m_tabWidget->activateTab(i); @@ -1941,21 +2192,22 @@ void DolphinMainWindow::setupActions() } QAction *activateLastTab = actionCollection()->addAction(QStringLiteral("activate_last_tab")); - activateLastTab->setText(i18nc("@action:inmenu", "Activate Last Tab")); + activateLastTab->setIconText(i18nc("@action:inmenu", "Last Tab")); + activateLastTab->setText(i18nc("@action:inmenu", "Go to Last Tab")); activateLastTab->setEnabled(false); connect(activateLastTab, &QAction::triggered, m_tabWidget, &DolphinTabWidget::activateLastTab); actionCollection()->setDefaultShortcut(activateLastTab, Qt::ALT | Qt::Key_0); QAction *activateNextTab = actionCollection()->addAction(QStringLiteral("activate_next_tab")); activateNextTab->setIconText(i18nc("@action:inmenu", "Next Tab")); - activateNextTab->setText(i18nc("@action:inmenu", "Activate Next Tab")); + activateNextTab->setText(i18nc("@action:inmenu", "Go to Next Tab")); activateNextTab->setEnabled(false); connect(activateNextTab, &QAction::triggered, m_tabWidget, &DolphinTabWidget::activateNextTab); actionCollection()->setDefaultShortcuts(activateNextTab, nextTabKeys); QAction *activatePrevTab = actionCollection()->addAction(QStringLiteral("activate_prev_tab")); activatePrevTab->setIconText(i18nc("@action:inmenu", "Previous Tab")); - activatePrevTab->setText(i18nc("@action:inmenu", "Activate Previous Tab")); + activatePrevTab->setText(i18nc("@action:inmenu", "Go to Previous Tab")); activatePrevTab->setEnabled(false); connect(activatePrevTab, &QAction::triggered, m_tabWidget, &DolphinTabWidget::activatePrevTab); actionCollection()->setDefaultShortcuts(activatePrevTab, prevTabKeys); @@ -1981,6 +2233,15 @@ void DolphinMainWindow::setupActions() openInNewWindow->setText(i18nc("@action:inmenu", "Open in New Window")); openInNewWindow->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); connect(openInNewWindow, &QAction::triggered, this, &DolphinMainWindow::openInNewWindow); + + QAction *openInSplitViewAction = actionCollection()->addAction(QStringLiteral("open_in_split_view")); + openInSplitViewAction->setText(i18nc("@action:inmenu", "Open in Split View")); + openInSplitViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); + connect(openInSplitViewAction, &QAction::triggered, this, [this]() { + openInSplitView(QUrl()); + }); + + m_recentFiles = new KRecentFilesAction(this); } void DolphinMainWindow::setupDockWidgets() @@ -2015,14 +2276,14 @@ void DolphinMainWindow::setupDockWidgets() connect(infoPanel, &InformationPanel::urlActivated, this, &DolphinMainWindow::handleUrl); infoDock->setWidget(infoPanel); - QAction *infoAction = infoDock->toggleViewAction(); - createPanelAction(QIcon::fromTheme(QStringLiteral("dialog-information")), Qt::Key_F11, infoAction, QStringLiteral("show_information_panel")); + createPanelAction(QIcon::fromTheme(QStringLiteral("documentinfo")), Qt::Key_F11, infoDock, QStringLiteral("show_information_panel")); addDockWidget(Qt::RightDockWidgetArea, infoDock); connect(this, &DolphinMainWindow::urlChanged, infoPanel, &InformationPanel::setUrl); connect(this, &DolphinMainWindow::selectionChanged, infoPanel, &InformationPanel::setSelection); connect(this, &DolphinMainWindow::requestItemInfo, infoPanel, &InformationPanel::requestDelayedItemInfo); connect(this, &DolphinMainWindow::fileItemsChanged, infoPanel, &InformationPanel::slotFilesItemChanged); + connect(this, &DolphinMainWindow::settingsChanged, infoPanel, &InformationPanel::readSettings); #endif // i18n: This is the last paragraph for the "What's This"-texts of all four panels. @@ -2059,8 +2320,7 @@ void DolphinMainWindow::setupDockWidgets() foldersPanel->setCustomContextMenuActions({lockLayoutAction}); foldersDock->setWidget(foldersPanel); - QAction *foldersAction = foldersDock->toggleViewAction(); - createPanelAction(QIcon::fromTheme(QStringLiteral("folder")), Qt::Key_F7, foldersAction, QStringLiteral("show_folders_panel")); + createPanelAction(QIcon::fromTheme(QStringLiteral("folder")), Qt::Key_F7, foldersDock, QStringLiteral("show_folders_panel")); addDockWidget(Qt::LeftDockWidgetArea, foldersDock); connect(this, &DolphinMainWindow::urlChanged, foldersPanel, &FoldersPanel::setUrl); @@ -2090,6 +2350,7 @@ void DolphinMainWindow::setupDockWidgets() DolphinDockWidget *terminalDock = new DolphinDockWidget(i18nc("@title:window Shell terminal", "Terminal")); terminalDock->setLocked(lock); terminalDock->setObjectName(QStringLiteral("terminalDock")); + terminalDock->setContentsMargins(0, 0, 0, 0); m_terminalPanel = new TerminalPanel(terminalDock); m_terminalPanel->setCustomContextMenuActions({lockLayoutAction}); terminalDock->setWidget(m_terminalPanel); @@ -2099,8 +2360,7 @@ void DolphinMainWindow::setupDockWidgets() connect(terminalDock, &DolphinDockWidget::visibilityChanged, m_terminalPanel, &TerminalPanel::dockVisibilityChanged); connect(terminalDock, &DolphinDockWidget::visibilityChanged, this, &DolphinMainWindow::slotTerminalPanelVisibilityChanged); - QAction *terminalAction = terminalDock->toggleViewAction(); - createPanelAction(QIcon::fromTheme(QStringLiteral("dialog-scripts")), Qt::Key_F4, terminalAction, QStringLiteral("show_terminal_panel")); + createPanelAction(QIcon::fromTheme(QStringLiteral("dialog-scripts")), Qt::Key_F4, terminalDock, QStringLiteral("show_terminal_panel")); addDockWidget(Qt::BottomDockWidgetArea, terminalDock); connect(this, &DolphinMainWindow::urlChanged, m_terminalPanel, &TerminalPanel::setUrl); @@ -2117,7 +2377,7 @@ void DolphinMainWindow::setupDockWidgets() "The location in the terminal will always match the folder " "view so you can navigate using either.The terminal " "panel is not needed for basic computer usage but can be useful " - "for advanced tasks. To learn more about terminals use the help " + "for advanced tasks. To learn more about terminals use the help features " "in a standalone terminal application like Konsole.")); terminalDock->setWhatsThis(xi18nc("@info:whatsthis", "This is " @@ -2125,11 +2385,18 @@ void DolphinMainWindow::setupDockWidgets() "normal terminal but will match the location of the folder view " "so you can navigate using either.The terminal panel " "is not needed for basic computer usage but can be useful for " - "advanced tasks. To learn more about terminals use the help in a " + "advanced tasks. To learn more about terminals use the help features in a " "standalone terminal application like Konsole.") + panelWhatsThis); - } -#endif + + QAction *focusTerminalPanel = actionCollection()->addAction(QStringLiteral("focus_terminal_panel")); + focusTerminalPanel->setText(i18nc("@action:inmenu Tools", "Focus Terminal Panel")); + focusTerminalPanel->setToolTip(i18nc("@info:tooltip", "Move keyboard focus to and from the Terminal panel.")); + focusTerminalPanel->setIcon(QIcon::fromTheme(QStringLiteral("swap-panels"))); + actionCollection()->setDefaultShortcut(focusTerminalPanel, Qt::CTRL | Qt::SHIFT | Qt::Key_F4); + connect(focusTerminalPanel, &QAction::triggered, this, &DolphinMainWindow::toggleTerminalPanelFocus); + } // endif "shell_access" allowed +#endif // HAVE_TERMINAL if (GeneralSettings::version() < 200) { infoDock->hide(); @@ -2146,8 +2413,7 @@ void DolphinMainWindow::setupDockWidgets() m_placesPanel->setCustomContextMenuActions({lockLayoutAction}); placesDock->setWidget(m_placesPanel); - QAction *placesAction = placesDock->toggleViewAction(); - createPanelAction(QIcon::fromTheme(QStringLiteral("compass")), Qt::Key_F9, placesAction, QStringLiteral("show_places_panel")); + createPanelAction(QIcon::fromTheme(QStringLiteral("compass")), Qt::Key_F9, placesDock, QStringLiteral("show_places_panel")); addDockWidget(Qt::LeftDockWidgetArea, placesDock); connect(m_placesPanel, &PlacesPanel::placeActivated, this, &DolphinMainWindow::slotPlaceActivated); @@ -2156,9 +2422,11 @@ void DolphinMainWindow::setupDockWidgets() connect(m_placesPanel, &PlacesPanel::newWindowRequested, this, [this](const QUrl &url) { Dolphin::openNewWindow({url}, this); }); + connect(m_placesPanel, &PlacesPanel::openInSplitViewRequested, this, &DolphinMainWindow::openInSplitView); connect(m_placesPanel, &PlacesPanel::errorMessage, this, &DolphinMainWindow::showErrorMessage); connect(this, &DolphinMainWindow::urlChanged, m_placesPanel, &PlacesPanel::setUrl); connect(placesDock, &DolphinDockWidget::visibilityChanged, &DolphinUrlNavigatorsController::slotPlacesPanelVisibilityChanged); + connect(placesDock, &DolphinDockWidget::visibilityChanged, this, &DolphinMainWindow::slotPlacesPanelVisibilityChanged); connect(this, &DolphinMainWindow::settingsChanged, m_placesPanel, &PlacesPanel::readSettings); connect(m_placesPanel, &PlacesPanel::storageTearDownRequested, this, &DolphinMainWindow::slotStorageTearDownFromPlacesRequested); connect(m_placesPanel, &PlacesPanel::storageTearDownExternallyRequested, this, &DolphinMainWindow::slotStorageTearDownExternallyRequested); @@ -2170,9 +2438,9 @@ void DolphinMainWindow::setupDockWidgets() actionShowAllPlaces->setWhatsThis(i18nc("@info:whatsthis", "This displays " "all places in the places panel that have been hidden. They will " - "appear semi-transparent unless you uncheck their hide property.")); + "appear semi-transparent and allow you to uncheck their \"Hide\" property.")); - connect(actionShowAllPlaces, &QAction::triggered, this, [actionShowAllPlaces, this](bool checked) { + connect(actionShowAllPlaces, &QAction::triggered, this, [this](bool checked) { m_placesPanel->setShowAll(checked); }); connect(m_placesPanel, &PlacesPanel::allPlacesShownChanged, actionShowAllPlaces, &QAction::setChecked); @@ -2200,6 +2468,13 @@ void DolphinMainWindow::setupDockWidgets() " to display it again.") + panelWhatsThis); + QAction *focusPlacesPanel = actionCollection()->addAction(QStringLiteral("focus_places_panel")); + focusPlacesPanel->setText(i18nc("@action:inmenu View", "Focus Places Panel")); + focusPlacesPanel->setToolTip(i18nc("@info:tooltip", "Move keyboard focus to and from the Places panel.")); + focusPlacesPanel->setIcon(QIcon::fromTheme(QStringLiteral("swap-panels"))); + actionCollection()->setDefaultShortcut(focusPlacesPanel, Qt::CTRL | Qt::Key_P); + connect(focusPlacesPanel, &QAction::triggered, this, &DolphinMainWindow::togglePlacesPanelFocus); + // Add actions into the "Panels" menu KActionMenu *panelsMenu = new KActionMenu(i18nc("@action:inmenu View", "Show Panels"), this); actionCollection()->addAction(QStringLiteral("panels"), panelsMenu); @@ -2213,10 +2488,13 @@ void DolphinMainWindow::setupDockWidgets() panelsMenu->addAction(ac->action(QStringLiteral("show_folders_panel"))); panelsMenu->addAction(ac->action(QStringLiteral("show_terminal_panel"))); panelsMenu->addSeparator(); - panelsMenu->addAction(actionShowAllPlaces); panelsMenu->addAction(lockLayoutAction); + panelsMenu->addSeparator(); + panelsMenu->addAction(actionShowAllPlaces); + panelsMenu->addAction(focusPlacesPanel); + panelsMenu->addAction(ac->action(QStringLiteral("focus_terminal_panel"))); - connect(panelsMenu->menu(), &QMenu::aboutToShow, this, [actionShowAllPlaces, this] { + connect(panelsMenu->menu(), &QMenu::aboutToShow, this, [actionShowAllPlaces] { actionShowAllPlaces->setEnabled(DolphinPlacesModelSingleton::instance().placesModel()->hiddenCount()); }); } @@ -2235,7 +2513,7 @@ void DolphinMainWindow::updateFileAndEditActions() QAction *addToPlacesAction = col->action(QStringLiteral("add_to_places")); QAction *copyToOtherViewAction = col->action(QStringLiteral("copy_to_inactive_split_view")); QAction *moveToOtherViewAction = col->action(QStringLiteral("move_to_inactive_split_view")); - QAction *copyLocation = col->action(QString("copy_location")); + QAction *copyLocation = col->action(QStringLiteral("copy_location")); if (list.isEmpty()) { stateChanged(QStringLiteral("has_no_selection")); @@ -2267,30 +2545,79 @@ void DolphinMainWindow::updateFileAndEditActions() const bool enableMoveToTrash = capabilitiesSource.isLocal() && capabilitiesSource.supportsMoving(); renameAction->setEnabled(capabilitiesSource.supportsMoving()); - moveToTrashAction->setEnabled(enableMoveToTrash); + m_disabledActionNotifier->setDisabledReason(renameAction, i18nc("@info", "Cannot rename: You do not have permission to rename items in this folder.")); deleteAction->setEnabled(capabilitiesSource.supportsDeleting()); - deleteWithTrashShortcut->setEnabled(capabilitiesSource.supportsDeleting() && !enableMoveToTrash); + m_disabledActionNotifier->setDisabledReason(deleteAction, + i18nc("@info", "Cannot delete: You do not have permission to remove items from this folder.")); cutAction->setEnabled(capabilitiesSource.supportsMoving()); + m_disabledActionNotifier->setDisabledReason(cutAction, i18nc("@info", "Cannot cut: You do not have permission to move items from this folder.")); copyLocation->setEnabled(list.length() == 1); showTarget->setEnabled(list.length() == 1 && list.at(0).isLink()); duplicateAction->setEnabled(capabilitiesSource.supportsWriting()); + m_disabledActionNotifier->setDisabledReason(duplicateAction, + i18nc("@info", "Cannot duplicate here: You do not have permission to create items in this folder.")); + + if (enableMoveToTrash) { + moveToTrashAction->setEnabled(true); + deleteWithTrashShortcut->setEnabled(false); + m_disabledActionNotifier->clearDisabledReason(deleteWithTrashShortcut); + } else { + moveToTrashAction->setEnabled(false); + deleteWithTrashShortcut->setEnabled(capabilitiesSource.supportsDeleting()); + m_disabledActionNotifier->setDisabledReason(deleteWithTrashShortcut, + i18nc("@info", "Cannot delete: You do not have permission to remove items from this folder.")); + } } - if (m_tabWidget->currentTabPage()->splitViewEnabled()) { + if (!m_tabWidget->currentTabPage()->splitViewEnabled()) { + // No need to set the disabled reason here, as it's obvious to the user that the reason is the split view being disabled. + copyToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->clearDisabledReason(copyToOtherViewAction); + moveToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->clearDisabledReason(moveToOtherViewAction); + } else if (list.isEmpty()) { + copyToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->setDisabledReason(copyToOtherViewAction, i18nc("@info", "Cannot copy to other view: No files selected.")); + moveToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->setDisabledReason(moveToOtherViewAction, i18nc("@info", "Cannot move to other view: No files selected.")); + } else { DolphinTabPage *tabPage = m_tabWidget->currentTabPage(); KFileItem capabilitiesDestination; if (tabPage->primaryViewActive()) { - capabilitiesDestination = tabPage->secondaryViewContainer()->url(); + capabilitiesDestination = tabPage->secondaryViewContainer()->rootItem(); } else { - capabilitiesDestination = tabPage->primaryViewContainer()->url(); + capabilitiesDestination = tabPage->primaryViewContainer()->rootItem(); } - copyToOtherViewAction->setEnabled(capabilitiesDestination.isWritable()); - moveToOtherViewAction->setEnabled((list.isEmpty() || capabilitiesSource.supportsMoving()) && capabilitiesDestination.isWritable()); - } else { - copyToOtherViewAction->setEnabled(false); - moveToOtherViewAction->setEnabled(false); + const auto destUrl = capabilitiesDestination.url(); + const bool allNotTargetOrigin = std::all_of(list.cbegin(), list.cend(), [destUrl](const KFileItem &item) { + return item.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash) != destUrl; + }); + + if (!allNotTargetOrigin) { + copyToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->setDisabledReason(copyToOtherViewAction, + i18nc("@info", "Cannot copy to other view: The other view already contains these items.")); + moveToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->setDisabledReason(moveToOtherViewAction, + i18nc("@info", "Cannot move to other view: The other view already contains these items.")); + } else if (!capabilitiesDestination.isWritable()) { + copyToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->setDisabledReason( + copyToOtherViewAction, + i18nc("@info", "Cannot copy to other view: You do not have permission to write into the destination folder.")); + moveToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->setDisabledReason( + moveToOtherViewAction, + i18nc("@info", "Cannot move to other view: You do not have permission to write into the destination folder.")); + } else { + copyToOtherViewAction->setEnabled(true); + moveToOtherViewAction->setEnabled(capabilitiesSource.supportsMoving()); + m_disabledActionNotifier->setDisabledReason( + moveToOtherViewAction, + i18nc("@info", "Cannot move to other view: You do not have permission to move items from this folder.")); + } } } @@ -2301,7 +2628,7 @@ void DolphinMainWindow::updateViewActions() QAction *toggleFilterBarAction = actionCollection()->action(QStringLiteral("toggle_filter")); toggleFilterBarAction->setChecked(m_activeViewContainer->isFilterBarVisible()); - updateSplitAction(); + updateSplitActions(); } void DolphinMainWindow::updateGoActions() @@ -2330,7 +2657,7 @@ void DolphinMainWindow::refreshViews() updateWindowTitle(); } - updateSplitAction(); + updateSplitActions(); Q_EMIT settingsChanged(); } @@ -2344,14 +2671,12 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer *container) { connect(container, &DolphinViewContainer::showFilterBarChanged, this, &DolphinMainWindow::updateFilterBarAction); connect(container, &DolphinViewContainer::writeStateChanged, this, &DolphinMainWindow::slotWriteStateChanged); - connect(container, &DolphinViewContainer::searchModeEnabledChanged, this, &DolphinMainWindow::updateSearchAction); + slotWriteStateChanged(container->view()->isFolderWritable()); + connect(container, &DolphinViewContainer::searchBarVisibilityChanged, this, &DolphinMainWindow::updateSearchAction); connect(container, &DolphinViewContainer::captionChanged, this, &DolphinMainWindow::updateWindowTitle); connect(container, &DolphinViewContainer::tabRequested, this, &DolphinMainWindow::openNewTab); connect(container, &DolphinViewContainer::activeTabRequested, this, &DolphinMainWindow::openNewTabAndActivate); - const QAction *toggleSearchAction = actionCollection()->action(QStringLiteral("toggle_search")); - connect(toggleSearchAction, &QAction::triggered, container, &DolphinViewContainer::setSearchModeEnabled); - // Make the toggled state of the selection mode actions visually follow the selection mode state of the view. auto toggleSelectionModeAction = actionCollection()->action(QStringLiteral("toggle_selection_mode")); toggleSelectionModeAction->setChecked(m_activeViewContainer->isSelectionModeEnabled()); @@ -2372,6 +2697,7 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer *container) connect(view, &DolphinView::goForwardRequested, this, &DolphinMainWindow::goForward); connect(view, &DolphinView::urlActivated, this, &DolphinMainWindow::handleUrl); connect(view, &DolphinView::goUpRequested, this, &DolphinMainWindow::goUp); + connect(view, &DolphinView::doubleClickViewBackground, this, &DolphinMainWindow::slotDoubleClickViewBackground); connect(container->urlNavigatorInternalWithHistory(), &KUrlNavigator::urlChanged, this, &DolphinMainWindow::changeUrl); connect(container->urlNavigatorInternalWithHistory(), &KUrlNavigator::historyChanged, this, &DolphinMainWindow::updateHistory); @@ -2388,25 +2714,60 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer *container) connect(navigator, &KUrlNavigator::newWindowRequested, this, &DolphinMainWindow::openNewWindow); } -void DolphinMainWindow::updateSplitAction() +void DolphinMainWindow::updateSplitActions() { - QAction *splitAction = actionCollection()->action(QStringLiteral("split_view")); + QAction *popoutSplitAction = actionCollection()->action(QStringLiteral("popout_split_view")); + + auto setActionPopupMode = [this](KActionMenu *action, QToolButton::ToolButtonPopupMode popupMode) { + action->setPopupMode(popupMode); + if (auto *buttonForAction = qobject_cast(toolBar()->widgetForAction(action))) { + buttonForAction->setPopupMode(popupMode); + } + }; + const DolphinTabPage *tabPage = m_tabWidget->currentTabPage(); if (tabPage->splitViewEnabled()) { if (GeneralSettings::closeActiveSplitView() ? tabPage->primaryViewActive() : !tabPage->primaryViewActive()) { - splitAction->setText(i18nc("@action:intoolbar Close left view", "Close")); - splitAction->setToolTip(i18nc("@info", "Close left view")); - splitAction->setIcon(QIcon::fromTheme(QStringLiteral("view-left-close"))); + m_splitViewAction->setText(i18nc("@action:intoolbar Close left view", "Close")); + m_splitViewAction->setToolTip(i18nc("@info", "Close left view")); + m_splitViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-left-close"))); + m_splitViewMenuAction->setText(i18nc("@action:inmenu Close left view", "Close Left View")); + + popoutSplitAction->setText(i18nc("@action:intoolbar Move left view to a new window", "Pop out Left View")); + popoutSplitAction->setToolTip(i18nc("@info", "Move left view to a new window")); } else { - splitAction->setText(i18nc("@action:intoolbar Close right view", "Close")); - splitAction->setToolTip(i18nc("@info", "Close right view")); - splitAction->setIcon(QIcon::fromTheme(QStringLiteral("view-right-close"))); + m_splitViewAction->setText(i18nc("@action:intoolbar Close right view", "Close")); + m_splitViewAction->setToolTip(i18nc("@info", "Close right view")); + m_splitViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-right-close"))); + m_splitViewMenuAction->setText(i18nc("@action:inmenu Close left view", "Close Right View")); + + popoutSplitAction->setText(i18nc("@action:intoolbar Move right view to a new window", "Pop out Right View")); + popoutSplitAction->setToolTip(i18nc("@info", "Move right view to a new window")); + } + popoutSplitAction->setEnabled(true); + if (!m_splitViewAction->menu()) { + setActionPopupMode(m_splitViewAction, QToolButton::MenuButtonPopup); + m_splitViewAction->setMenu(new QMenu); + m_splitViewAction->addAction(popoutSplitAction); } } else { - splitAction->setText(i18nc("@action:intoolbar Split view", "Split")); - splitAction->setToolTip(i18nc("@info", "Split view")); - splitAction->setIcon(QIcon::fromTheme(QStringLiteral("view-right-new"))); + m_splitViewAction->setText(i18nc("@action:intoolbar Split view", "Split")); + m_splitViewMenuAction->setText(m_splitViewAction->text()); + m_splitViewAction->setToolTip(i18nc("@info", "Split view")); + m_splitViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); + popoutSplitAction->setText(i18nc("@action:intoolbar Move view in focus to a new window", "Pop out")); + popoutSplitAction->setEnabled(false); + if (m_splitViewAction->menu()) { + m_splitViewAction->removeAction(popoutSplitAction); + m_splitViewAction->menu()->deleteLater(); + m_splitViewAction->setMenu(nullptr); + setActionPopupMode(m_splitViewAction, QToolButton::DelayedPopup); + } } + + // Update state from toolbar action + m_splitViewMenuAction->setToolTip(m_splitViewAction->toolTip()); + m_splitViewMenuAction->setIcon(m_splitViewAction->icon()); } void DolphinMainWindow::updateAllowedToolbarAreas() @@ -2422,6 +2783,12 @@ void DolphinMainWindow::updateAllowedToolbarAreas() } } +void DolphinMainWindow::updateNavigatorsBackground() +{ + auto navigators = static_cast(actionCollection()->action(QStringLiteral("url_navigators"))); + navigators->setBackgroundEnabled(navigators->isInToolbar()); +} + bool DolphinMainWindow::isKompareInstalled() const { static bool initialized = false; @@ -2435,18 +2802,14 @@ bool DolphinMainWindow::isKompareInstalled() const return installed; } -void DolphinMainWindow::createPanelAction(const QIcon &icon, const QKeySequence &shortcut, QAction *dockAction, const QString &actionName) +void DolphinMainWindow::createPanelAction(const QIcon &icon, const QKeySequence &shortcut, QDockWidget *dockWidget, const QString &actionName) { - QAction *panelAction = actionCollection()->addAction(actionName); - panelAction->setCheckable(true); - panelAction->setChecked(dockAction->isChecked()); - panelAction->setText(dockAction->text()); - panelAction->setIcon(icon); + auto dockAction = dockWidget->toggleViewAction(); dockAction->setIcon(icon); - actionCollection()->setDefaultShortcut(panelAction, shortcut); + dockAction->setEnabled(true); - connect(panelAction, &QAction::triggered, dockAction, &QAction::trigger); - connect(dockAction, &QAction::toggled, panelAction, &QAction::setChecked); + QAction *panelAction = actionCollection()->addAction(actionName, dockAction); + actionCollection()->setDefaultShortcut(panelAction, shortcut); } // clang-format off void DolphinMainWindow::setupWhatsThis() @@ -2504,15 +2867,23 @@ void DolphinMainWindow::setupWhatsThis() // Help menu + auto setStandardActionWhatsThis = [this](KStandardAction::StandardAction actionId, + const QString &whatsThis) { + // Check for the existence of an action since it can be restricted through the Kiosk system + if (auto *action = actionCollection()->action(KStandardAction::name(actionId))) { + action->setWhatsThis(whatsThis); + } + }; + // i18n: If the external link isn't available in your language it might make // sense to state the external link's language in brackets to not // frustrate the user. If there are multiple languages that the user might // know with a reasonable chance you might want to have 2 external links. // The same might be true for any external link you translate. - actionCollection()->action(KStandardAction::name(KStandardAction::HelpContents))->setWhatsThis(xi18nc("@info:whatsthis handbook", "This opens the Handbook for this application. It provides explanations for every part of Dolphin.If you want more elaborate introductions to the different features of Dolphin click here. It will open the dedicated page in the KDE UserBase Wiki.")); + setStandardActionWhatsThis(KStandardAction::HelpContents, xi18nc("@info:whatsthis handbook", "This opens the Handbook for this application. It provides explanations for every part of Dolphin.If you want more elaborate introductions to the different features of Dolphin click here. It will open the dedicated page in the KDE UserBase Wiki.")); // (The i18n call should be completely in the line following the i18n: comment without any line breaks within the i18n call or the comment might not be correctly extracted. See: https://commits.kde.org/kxmlgui/a31135046e1b3335b5d7bbbe6aa9a883ce3284c1 ) - actionCollection()->action(KStandardAction::name(KStandardAction::WhatsThis))->setWhatsThis( + setStandardActionWhatsThis(KStandardAction::WhatsThis, xi18nc("@info:whatsthis whatsthis button", "This is the button that invokes the help feature you are " "using right now! Click it, then click any component of this " @@ -2524,7 +2895,7 @@ void DolphinMainWindow::setupWhatsThis() "UserBase Wiki.The \"What's this?\" help is " "missing in most other windows so don't get too used to this.")); - actionCollection()->action(KStandardAction::name(KStandardAction::ReportBug))->setWhatsThis( + setStandardActionWhatsThis(KStandardAction::ReportBug, xi18nc("@info:whatsthis","This opens a " "window that will guide you through reporting errors or flaws " "in this application or in other KDE software." @@ -2533,7 +2904,7 @@ void DolphinMainWindow::setupWhatsThis() "" "click here.")); - actionCollection()->action(KStandardAction::name(KStandardAction::Donate))->setWhatsThis( + setStandardActionWhatsThis(KStandardAction::Donate, xi18nc("@info:whatsthis", "This opens a " "web page where you can donate to " "support the continued work on this application and many " @@ -2545,18 +2916,18 @@ void DolphinMainWindow::setupWhatsThis() "KDE e.V. is the non-profit " "organization behind the KDE community.")); - actionCollection()->action(KStandardAction::name(KStandardAction::SwitchApplicationLanguage))->setWhatsThis( + setStandardActionWhatsThis(KStandardAction::SwitchApplicationLanguage, xi18nc("@info:whatsthis", "With this you can change the language this application uses." "You can even set secondary languages which will be used " "if texts are not available in your preferred language.")); - actionCollection()->action(KStandardAction::name(KStandardAction::AboutApp))->setWhatsThis( + setStandardActionWhatsThis(KStandardAction::AboutApp, xi18nc("@info:whatsthis","This opens a " "window that informs you about the version, license, " "used libraries and maintainers of this application.")); - actionCollection()->action(KStandardAction::name(KStandardAction::AboutKDE))->setWhatsThis( + setStandardActionWhatsThis(KStandardAction::AboutKDE, xi18nc("@info:whatsthis","This opens a " "window with information about KDE. " "The KDE community are the people behind this free software." @@ -2604,23 +2975,44 @@ void DolphinMainWindow::saveNewToolbarConfig() m_tabWidget->currentTabPage()->insertNavigatorsWidget(navigators); } updateAllowedToolbarAreas(); + updateNavigatorsBackground(); (static_cast(actionCollection()->action(KStandardAction::name(KStandardAction::HamburgerMenu))))->hideActionsOf(toolBar()); } -void DolphinMainWindow::focusTerminalPanel() +void DolphinMainWindow::toggleTerminalPanelFocus() { - if (m_terminalPanel->isVisible()) { - if (m_terminalPanel->terminalHasFocus()) { - m_activeViewContainer->view()->setFocus(Qt::FocusReason::ShortcutFocusReason); - actionCollection()->action(QStringLiteral("focus_terminal_panel"))->setText(i18nc("@action:inmenu Tools", "Focus Terminal Panel")); - } else { - m_terminalPanel->setFocus(Qt::FocusReason::ShortcutFocusReason); - actionCollection()->action(QStringLiteral("focus_terminal_panel"))->setText(i18nc("@action:inmenu Tools", "Defocus Terminal Panel")); - } - } else { - actionCollection()->action(QStringLiteral("show_terminal_panel"))->trigger(); + if (!m_terminalPanel->isVisible()) { + actionCollection()->action(QStringLiteral("show_terminal_panel"))->trigger(); // Also moves focus to the panel. actionCollection()->action(QStringLiteral("focus_terminal_panel"))->setText(i18nc("@action:inmenu Tools", "Defocus Terminal Panel")); + return; + } + + if (m_terminalPanel->terminalHasFocus()) { + m_activeViewContainer->view()->setFocus(Qt::FocusReason::ShortcutFocusReason); + actionCollection()->action(QStringLiteral("focus_terminal_panel"))->setText(i18nc("@action:inmenu Tools", "Focus Terminal Panel")); + return; } + + m_terminalPanel->setFocus(Qt::FocusReason::ShortcutFocusReason); + actionCollection()->action(QStringLiteral("focus_terminal_panel"))->setText(i18nc("@action:inmenu Tools", "Defocus Terminal Panel")); +} + +void DolphinMainWindow::togglePlacesPanelFocus() +{ + if (!m_placesPanel->isVisible()) { + actionCollection()->action(QStringLiteral("show_places_panel"))->trigger(); // Also moves focus to the panel. + actionCollection()->action(QStringLiteral("focus_places_panel"))->setText(i18nc("@action:inmenu View", "Defocus Terminal Panel")); + return; + } + + if (m_placesPanel->hasFocus()) { + m_activeViewContainer->view()->setFocus(Qt::FocusReason::ShortcutFocusReason); + actionCollection()->action(QStringLiteral("focus_places_panel"))->setText(i18nc("@action:inmenu View", "Focus Places Panel")); + return; + } + + m_placesPanel->setFocus(Qt::FocusReason::ShortcutFocusReason); + actionCollection()->action(QStringLiteral("focus_places_panel"))->setText(i18nc("@action:inmenu View", "Defocus Places Panel")); } DolphinMainWindow::UndoUiInterface::UndoUiInterface() @@ -2637,7 +3029,7 @@ void DolphinMainWindow::UndoUiInterface::jobError(KIO::Job *job) DolphinMainWindow *mainWin = qobject_cast(parentWidget()); if (mainWin) { DolphinViewContainer *container = mainWin->activeViewContainer(); - container->showMessage(job->errorString(), DolphinViewContainer::Error); + container->showMessage(job->errorString(), KMessageWidget::Error); } else { KIO::FileUndoManager::UiInterface::jobError(job); } @@ -2652,3 +3044,42 @@ bool DolphinMainWindow::isItemVisibleInAnyView(const QString &urlOfItem) { return m_tabWidget->isItemVisibleInAnyView(QUrl::fromUserInput(urlOfItem)); } + +void DolphinMainWindow::slotDoubleClickViewBackground(Qt::MouseButton button) +{ + if (button != Qt::MouseButton::LeftButton) { + // only handle left mouse button for now + return; + } + + GeneralSettings *settings = GeneralSettings::self(); + QString clickAction = settings->doubleClickViewAction(); + + DolphinView *view = activeViewContainer()->view(); + if (view == nullptr || clickAction == "none") { + return; + } + + if (clickAction == customCommand) { + // run custom command set by the user + QString path = view->url().toLocalFile(); + QString clickCustomAction = settings->doubleClickViewCustomAction(); + clickCustomAction.replace("{path}", path.prepend('"').append('"')); + + m_job = new KIO::CommandLauncherJob(clickCustomAction); + m_job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this)); + m_job->start(); + + } else { + // get the action set by the user and trigger it + const KActionCollection *actions = actionCollection(); + QAction *action = actions->action(clickAction); + if (action == nullptr) { + qCWarning(DolphinDebug) << QStringLiteral("Double-click view: action `%1` was not found").arg(clickAction); + return; + } + action->trigger(); + } +} + +#include "moc_dolphinmainwindow.cpp"