]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/dolphinmainwindow.cpp
DolphinMainWindow: show a banner when the user presses the shortcut of a disabled...
[dolphin.git] / src / dolphinmainwindow.cpp
index 0d31df2da08bacff1ffdcb61758020d6a8c182f8..4cba465540cb4f8b43991e2682145ce2cf4bcbbe 100644 (file)
@@ -37,6 +37,7 @@
 #include <KActionCollection>
 #include <KActionMenu>
 #include <KAuthorized>
+#include <KColorSchemeManager>
 #include <KConfig>
 #include <KConfigGui>
 #include <KDualAction>
@@ -48,7 +49,6 @@
 #include <KJobWidgets>
 #include <KLocalizedString>
 #include <KMessageBox>
-#include <KMoreToolsMenuFactory>
 #include <KProtocolInfo>
 #include <KProtocolManager>
 #include <KShell>
@@ -80,6 +80,7 @@
 #include <QStandardPaths>
 #include <QTimer>
 #include <QToolButton>
+#include <QtConcurrentRun>
 
 #include <algorithm>
 
@@ -96,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;
 }
 
@@ -109,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);
 
@@ -128,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();
@@ -168,6 +179,11 @@ DolphinMainWindow::DolphinMainWindow()
     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);
@@ -466,7 +482,7 @@ void DolphinMainWindow::openNewWindow(const QUrl &url)
 void DolphinMainWindow::slotSplitViewChanged()
 {
     m_tabWidget->currentTabPage()->setSplitViewEnabled(GeneralSettings::splitView(), WithAnimation);
-    updateSplitAction();
+    updateSplitActions();
 }
 
 void DolphinMainWindow::openInNewTab()
@@ -677,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<void>::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<void>(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)
@@ -787,6 +859,10 @@ void DolphinMainWindow::updatePasteAction()
     QAction *pasteAction = actionCollection()->action(KStandardAction::name(KStandardAction::Paste));
     QPair<bool, QString> 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);
 }
 
@@ -921,9 +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();
@@ -1127,15 +1214,21 @@ void DolphinMainWindow::toggleShowMenuBar()
 QPointer<QAction> DolphinMainWindow::preferredSearchTool()
 {
     m_searchTools.clear();
-    KMoreToolsMenuFactory("dolphin/search-tools").fillMenuFromGroupingNames(&m_searchTools, {"files-find"}, m_activeViewContainer->url());
-    QList<QAction *> 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;
 }
 
@@ -1300,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<DolphinContextMenu> 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) {
@@ -1562,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."
                                    "<nl/>You can drag and drop items between windows."));
     newWindow->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
 
@@ -1571,8 +1669,8 @@ void DolphinMainWindow::setupActions()
     newTab->setText(i18nc("@action:inmenu File", "New Tab"));
     newTab->setWhatsThis(xi18nc("@info:whatsthis",
                                 "This opens a new "
-                                "<emphasis>Tab</emphasis> with the current location and view.<nl/>"
-                                "A tab is an additional view within this window. "
+                                "<emphasis>Tab</emphasis> with the current location."
+                                "<nl/>Tabs allow you to quickly switch between multiple locations and views within this window. "
                                 "You can drag and drop items between tabs."));
     actionCollection()->setDefaultShortcut(newTab, Qt::CTRL | Qt::Key_T);
     connect(newTab, &QAction::triggered, this, &DolphinMainWindow::openNewActivatedTab);
@@ -1587,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."));
@@ -1641,9 +1740,10 @@ void DolphinMainWindow::setupActions()
     m_actionTextHelper->registerTextWhenNothingIsSelected(copyToOtherViewAction, i18nc("@action:inmenu", "Copy to Other View…"));
     copyToOtherViewAction->setWhatsThis(xi18nc("@info:whatsthis Copy",
                                                "This copies the selected items from "
-                                               "the <emphasis>active</emphasis> 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);
 
@@ -1652,9 +1752,10 @@ void DolphinMainWindow::setupActions()
     m_actionTextHelper->registerTextWhenNothingIsSelected(moveToOtherViewAction, i18nc("@action:inmenu", "Move to Other View…"));
     moveToOtherViewAction->setWhatsThis(xi18nc("@info:whatsthis Move",
                                                "This moves the selected items from "
-                                               "the <emphasis>active</emphasis> 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);
 
@@ -1664,7 +1765,7 @@ void DolphinMainWindow::setupActions()
     showFilterBar->setWhatsThis(xi18nc("@info:whatsthis",
                                        "This opens the "
                                        "<emphasis>Filter Bar</emphasis> at the bottom of the window.<nl/> "
-                                       "There you can enter 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});
@@ -1687,10 +1788,10 @@ void DolphinMainWindow::setupActions()
     searchAction->setToolTip(i18nc("@info:tooltip", "Search for files and folders"));
     searchAction->setWhatsThis(xi18nc("@info:whatsthis find",
                                       "<para>This helps you "
-                                      "find files and folders by opening a <emphasis>find bar</emphasis>. "
+                                      "find files and folders by opening a <emphasis>search bar</emphasis>. "
                                       "There you can enter search terms and specify settings to find the "
-                                      "objects you are looking for.</para><para>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.</para><para>Use this help again on "
+                                      "the search bar so we can have a look at it while the settings are "
                                       "explained.</para>"));
 
     // toggle_search acts as a copy of the main searchAction to be used mainly
@@ -1718,7 +1819,7 @@ void DolphinMainWindow::setupActions()
         "</para>"));
     toggleSelectionModeAction->setIcon(QIcon::fromTheme(QStringLiteral("quickwizard")));
     toggleSelectionModeAction->setCheckable(true);
-    actionCollection()->setDefaultShortcut(toggleSelectionModeAction, Qt::Key_Space );
+    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
@@ -1742,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 <emphasis>not</emphasis> selected instead."));
+                                         "items that you have currently <emphasis>not</emphasis> 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);
@@ -1755,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",
-                               "<para>This splits "
-                               "the folder view below into two autonomous views.</para><para>This "
-                               "way you can see two locations at once and move items between them "
-                               "quickly.</para>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<KActionMenu>(QStringLiteral("split_view"));
+    m_splitViewMenuAction = actionCollection()->addAction(QStringLiteral("split_view_menu"));
+
+    m_splitViewAction->setWhatsThis(xi18nc("@info:whatsthis split",
+                                           "<para>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.</para><para>The view that is not \"in focus\" will be dimmed. "
+                                           "</para>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);
@@ -1847,10 +1964,10 @@ void DolphinMainWindow::setupActions()
     undoAction->setWhatsThis(xi18nc("@info:whatsthis",
                                     "This undoes "
                                     "the last change you made to files or folders.<nl/>"
-                                    "Such changes include <interface>creatingrenaming</interface> "
+                                    "Such changes include <interface>creating</interface>, <interface>renaming</interface> "
                                     "and <interface>moving</interface> them to a different location "
-                                    "or to the <filename>Trash</filename>. <nl/>Changes that can't "
-                                    "be undone will ask for your confirmation."));
+                                    "or to the <filename>Trash</filename>. <nl/>Any changes that cannot be undone "
+                                    "will ask for your confirmation beforehand."));
     undoAction->setEnabled(false); // undo should be disabled by default
 
     {
@@ -1876,8 +1993,8 @@ void DolphinMainWindow::setupActions()
     homeAction->setWhatsThis(xi18nc("@info:whatsthis",
                                     "Go to your "
                                     "<filename>Home</filename> folder.<nl/>Every user account "
-                                    "has their own <filename>Home</filename> that contains their data "
-                                    "including folders that contain personal application data."));
+                                    "has their own <filename>Home</filename> 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"));
@@ -1900,7 +2017,7 @@ void DolphinMainWindow::setupActions()
         openTerminal->setText(i18nc("@action:inmenu Tools", "Open Terminal"));
         openTerminal->setWhatsThis(xi18nc("@info:whatsthis",
                                           "<para>This opens a <emphasis>terminal</emphasis> application for the viewed location.</para>"
-                                          "<para>To learn more about terminals use the help in the terminal application.</para>"));
+                                          "<para>To learn more about terminals use the help features in the terminal application.</para>"));
         openTerminal->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal")));
         actionCollection()->setDefaultShortcut(openTerminal, Qt::SHIFT | Qt::Key_F4);
         connect(openTerminal, &QAction::triggered, this, &DolphinMainWindow::openTerminal);
@@ -1910,7 +2027,7 @@ void DolphinMainWindow::setupActions()
         openTerminalHere->setText(i18nc("@action:inmenu Tools", "Open Terminal Here"));
         openTerminalHere->setWhatsThis(xi18nc("@info:whatsthis",
                                               "<para>This opens <emphasis>terminal</emphasis> applications for the selected items' locations.</para>"
-                                              "<para>To learn more about terminals use the help in the terminal application.</para>"));
+                                              "<para>To learn more about terminals use the help features in the terminal application.</para>"));
         openTerminalHere->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal")));
         actionCollection()->setDefaultShortcut(openTerminalHere, Qt::SHIFT | Qt::ALT | Qt::Key_F4);
         connect(openTerminalHere, &QAction::triggered, this, &DolphinMainWindow::openTerminalHere);
@@ -1936,10 +2053,10 @@ void DolphinMainWindow::setupActions()
     KToggleAction *showMenuBar = KStandardAction::showMenubar(nullptr, nullptr, actionCollection());
     showMenuBar->setWhatsThis(xi18nc("@info:whatsthis",
                                      "<para>This switches between having a <emphasis>Menubar</emphasis> "
-                                     "and having a <interface>%1</interface> button. Both "
+                                     "and having an <interface>%1</interface> button. Both "
                                      "contain mostly the same actions and configuration options.</para>"
-                                     "<para>The Menubar takes up more space but allows for fast and organised access to all "
-                                     "actions an application has to offer.</para><para>The <interface>%1</interface> button "
+                                     "<para>The Menubar takes up more space but allows for fast and organized access to all "
+                                     "actions an application has to offer.</para><para>The %1 button "
                                      "is simpler and small which makes triggering advanced actions more time consuming.</para>",
                                      hamburgerMenuAction->text().replace('&', "")));
     connect(showMenuBar,
@@ -1968,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);
@@ -1981,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);
@@ -2069,6 +2187,7 @@ void DolphinMainWindow::setupDockWidgets()
     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.
@@ -2135,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);
@@ -2161,7 +2281,7 @@ void DolphinMainWindow::setupDockWidgets()
                                   "<nl/>The location in the terminal will always match the folder "
                                   "view so you can navigate using either.</para><para>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.</para>"));
         terminalDock->setWhatsThis(xi18nc("@info:whatsthis",
                                           "<para>This is "
@@ -2169,7 +2289,7 @@ void DolphinMainWindow::setupDockWidgets()
                                           "normal terminal but will match the location of the folder view "
                                           "so you can navigate using either.</para><para>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.</para>")
                                    + panelWhatsThis);
     }
@@ -2214,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);
@@ -2260,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());
     });
 }
@@ -2311,16 +2431,43 @@ 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() && !list.isEmpty()) {
+    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;
 
@@ -2335,12 +2482,29 @@ void DolphinMainWindow::updateFileAndEditActions()
             return item.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash) != destUrl;
         });
 
-        copyToOtherViewAction->setEnabled(capabilitiesDestination.isWritable() && allNotTargetOrigin);
-        moveToOtherViewAction->setEnabled((list.isEmpty() || capabilitiesSource.supportsMoving()) && capabilitiesDestination.isWritable()
-                                          && allNotTargetOrigin);
-    } else {
-        copyToOtherViewAction->setEnabled(false);
-        moveToOtherViewAction->setEnabled(false);
+        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."));
+        }
     }
 }
 
@@ -2351,7 +2515,7 @@ void DolphinMainWindow::updateViewActions()
     QAction *toggleFilterBarAction = actionCollection()->action(QStringLiteral("toggle_filter"));
     toggleFilterBarAction->setChecked(m_activeViewContainer->isFilterBarVisible());
 
-    updateSplitAction();
+    updateSplitActions();
 }
 
 void DolphinMainWindow::updateGoActions()
@@ -2380,7 +2544,7 @@ void DolphinMainWindow::refreshViews()
         updateWindowTitle();
     }
 
-    updateSplitAction();
+    updateSplitActions();
 
     Q_EMIT settingsChanged();
 }
@@ -2439,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<QToolButton *>(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()
@@ -2494,8 +2689,6 @@ void DolphinMainWindow::createPanelAction(const QIcon &icon, const QKeySequence
 
     QAction *panelAction = actionCollection()->addAction(actionName, dockAction);
     actionCollection()->setDefaultShortcut(panelAction, shortcut);
-
-    connect(panelAction, &QAction::toggled, dockWidget, &QWidget::setVisible);
 }
 // clang-format off
 void DolphinMainWindow::setupWhatsThis()