X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/38c34eeca315c7be58e65d4d3fb72aaf7b866719..7d49cb567b595570f59156965332428c9490a04d:/src/dolphinmainwindow.cpp diff --git a/src/dolphinmainwindow.cpp b/src/dolphinmainwindow.cpp index 5d31a55dc..4cba46554 100644 --- a/src/dolphinmainwindow.cpp +++ b/src/dolphinmainwindow.cpp @@ -37,29 +37,23 @@ #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 @@ -70,7 +64,6 @@ #include #include -#include #include #include @@ -87,6 +80,13 @@ #include #include #include +#include + +#include + +#if HAVE_X11 +#include +#endif namespace { @@ -97,7 +97,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 +110,18 @@ 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_sessionSaveTimer(nullptr) + , m_sessionSaveWatcher(nullptr) + , m_sessionSaveScheduled(false) { Q_INIT_RESOURCE(dolphin); @@ -129,6 +135,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(); @@ -163,9 +173,17 @@ DolphinMainWindow::DolphinMainWindow() connect(m_actionHandler, &DolphinViewActionHandler::createDirectoryTriggered, this, &DolphinMainWindow::createDirectory); connect(m_actionHandler, &DolphinViewActionHandler::selectionModeChangeTriggered, this, &DolphinMainWindow::slotSetSelectionMode); + Q_CHECK_PTR(actionCollection()->action(QStringLiteral("create_dir"))); + m_newFileMenu->setNewFolderShortcutAction(actionCollection()->action(QStringLiteral("create_dir"))); + 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, DolphinViewContainer::Warning); + }); + setupDockWidgets(); setupGUI(Save | Create | ToolBar); @@ -265,6 +283,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 +299,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()); @@ -457,7 +482,7 @@ void DolphinMainWindow::openNewWindow(const QUrl &url) void DolphinMainWindow::slotSplitViewChanged() { m_tabWidget->currentTabPage()->setSplitViewEnabled(GeneralSettings::splitView(), WithAnimation); - updateSplitAction(); + updateSplitActions(); } void DolphinMainWindow::openInNewTab() @@ -497,21 +522,63 @@ 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(), DolphinViewContainer::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); @@ -606,7 +673,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 +693,74 @@ 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(); + + 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,20 +776,12 @@ 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(); } @@ -744,6 +859,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", "Could not paste: The clipboard is empty.") + : i18nc("@info", "Could not paste: You do not have permission to write into this folder.")); pasteAction->setText(pasteInfo.second); } @@ -801,10 +920,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 +949,10 @@ void DolphinMainWindow::slotAboutToShowForwardPopupMenu() { const KUrlNavigator *urlNavigator = m_activeViewContainer->urlNavigatorInternalWithHistory(); int entries = 0; - m_forwardAction->menu()->clear(); + QMenu *menu = m_forwardAction->popupMenu(); 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); } } @@ -877,10 +997,20 @@ void DolphinMainWindow::toggleSplitView() { DolphinTabPage *tabPage = m_tabWidget->currentTabPage(); tabPage->setSplitViewEnabled(!tabPage->splitViewEnabled(), WithAnimation); - + 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(); @@ -1084,15 +1214,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 +1272,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 +1291,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 +1368,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 +1393,17 @@ 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_dir")), + i18nc("@info", "Could not 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) { @@ -1423,7 +1551,7 @@ void DolphinMainWindow::activeViewChanged(DolphinViewContainer *viewContainer) 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); @@ -1469,7 +1597,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 +1612,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 +1647,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, 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 +1660,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 +1669,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 +1685,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 +1736,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,14 +1784,14 @@ 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 " + "items you are looking for.Use this help again on " + "the search bar so we can have a look at it while the settings are " "explained.")); // toggle_search acts as a copy of the main searchAction to be used mainly @@ -1687,6 +1819,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 +1830,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 +1843,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 +1856,30 @@ 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); + 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 +1891,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 +1925,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 +1935,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 +1964,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 +1976,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")); @@ -1860,7 +2017,7 @@ void DolphinMainWindow::setupActions() 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.")); + "To learn more about terminals use the help features in the terminal application.")); openTerminal->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); actionCollection()->setDefaultShortcut(openTerminal, Qt::SHIFT | Qt::Key_F4); connect(openTerminal, &QAction::triggered, this, &DolphinMainWindow::openTerminal); @@ -1870,7 +2027,7 @@ 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.")); + "To learn more about terminals use the help features in the terminal application.")); openTerminalHere->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); actionCollection()->setDefaultShortcut(openTerminalHere, Qt::SHIFT | Qt::ALT | Qt::Key_F4); connect(openTerminalHere, &QAction::triggered, this, &DolphinMainWindow::openTerminalHere); @@ -1896,10 +2053,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, @@ -1928,7 +2085,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 +2098,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 +2139,13 @@ 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-right-new"))); + connect(openInSplitViewAction, &QAction::triggered, this, [this]() { + openInSplitView(QUrl()); + }); } void DolphinMainWindow::setupDockWidgets() @@ -2015,14 +2180,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("dialog-information")), 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 +2224,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 +2254,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 +2264,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 +2281,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,7 +2289,7 @@ 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); } @@ -2146,8 +2310,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,6 +2319,7 @@ 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); @@ -2170,9 +2334,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); @@ -2216,7 +2380,7 @@ void DolphinMainWindow::setupDockWidgets() panelsMenu->addAction(actionShowAllPlaces); panelsMenu->addAction(lockLayoutAction); - connect(panelsMenu->menu(), &QMenu::aboutToShow, this, [actionShowAllPlaces, this] { + connect(panelsMenu->menu(), &QMenu::aboutToShow, this, [actionShowAllPlaces] { actionShowAllPlaces->setEnabled(DolphinPlacesModelSingleton::instance().placesModel()->hiddenCount()); }); } @@ -2267,30 +2431,80 @@ void DolphinMainWindow::updateFileAndEditActions() const bool enableMoveToTrash = capabilitiesSource.isLocal() && capabilitiesSource.supportsMoving(); renameAction->setEnabled(capabilitiesSource.supportsMoving()); - moveToTrashAction->setEnabled(enableMoveToTrash); + m_disabledActionNotifier->setDisabledReason(renameAction, + i18nc("@info", "Could not 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", "Could not delete: You do not have permission to remove items from this folder.")); cutAction->setEnabled(capabilitiesSource.supportsMoving()); + m_disabledActionNotifier->setDisabledReason(cutAction, i18nc("@info", "Could not 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", "Could not 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", "Could not 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", "Could not copy to other view: No files selected.")); + moveToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->setDisabledReason(moveToOtherViewAction, i18nc("@info", "Could not 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", "Could not copy to other view: The other view already contains these items.")); + moveToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->setDisabledReason(moveToOtherViewAction, + i18nc("@info", "Could not move to other view: The other view already contains these items.")); + } else if (!capabilitiesDestination.isWritable()) { + copyToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->setDisabledReason( + copyToOtherViewAction, + i18nc("@info", "Could not copy to other view: You do not have permission to write into the destination folder.")); + moveToOtherViewAction->setEnabled(false); + m_disabledActionNotifier->setDisabledReason( + moveToOtherViewAction, + i18nc("@info", "Could not 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", "Could not move to other view: You do not have permission to move items from this folder.")); + } } } @@ -2301,7 +2515,7 @@ void DolphinMainWindow::updateViewActions() QAction *toggleFilterBarAction = actionCollection()->action(QStringLiteral("toggle_filter")); toggleFilterBarAction->setChecked(m_activeViewContainer->isFilterBarVisible()); - updateSplitAction(); + updateSplitActions(); } void DolphinMainWindow::updateGoActions() @@ -2330,7 +2544,7 @@ void DolphinMainWindow::refreshViews() updateWindowTitle(); } - updateSplitAction(); + updateSplitActions(); Q_EMIT settingsChanged(); } @@ -2344,6 +2558,7 @@ void DolphinMainWindow::connectViewSignals(DolphinViewContainer *container) { connect(container, &DolphinViewContainer::showFilterBarChanged, this, &DolphinMainWindow::updateFilterBarAction); connect(container, &DolphinViewContainer::writeStateChanged, this, &DolphinMainWindow::slotWriteStateChanged); + slotWriteStateChanged(container->view()->isFolderWritable()); connect(container, &DolphinViewContainer::searchModeEnabledChanged, this, &DolphinMainWindow::updateSearchAction); connect(container, &DolphinViewContainer::captionChanged, this, &DolphinMainWindow::updateWindowTitle); connect(container, &DolphinViewContainer::tabRequested, this, &DolphinMainWindow::openNewTab); @@ -2388,25 +2603,56 @@ 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"))); + 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"))); + 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_splitViewAction->setToolTip(i18nc("@info", "Split view")); + m_splitViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-right-new"))); + 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->setText(m_splitViewAction->text()); + m_splitViewMenuAction->setToolTip(m_splitViewAction->toolTip()); + m_splitViewMenuAction->setIcon(m_splitViewAction->icon()); } void DolphinMainWindow::updateAllowedToolbarAreas() @@ -2435,18 +2681,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 +2746,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 +2774,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 +2783,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 +2795,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." @@ -2652,3 +2902,5 @@ bool DolphinMainWindow::isItemVisibleInAnyView(const QString &urlOfItem) { return m_tabWidget->isItemVisibleInAnyView(QUrl::fromUserInput(urlOfItem)); } + +#include "moc_dolphinmainwindow.cpp"