]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/dolphintabwidget.cpp
DolphinTabWidget: Allow specifying new tab position in openNewTab
[dolphin.git] / src / dolphintabwidget.cpp
index afb5462e1ad4fcdc9cff9fdd12a1b705d0c23ca4..7eae6f297ec3ef4154c6b31abf8144dbe028248f 100644 (file)
@@ -1,42 +1,31 @@
-/***************************************************************************
- * Copyright (C) 2014 by Emmanuel Pescosta <emmanuelpescosta099@gmail.com> *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, write to the                         *
- *   Free Software Foundation, Inc.,                                       *
- *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA            *
- ***************************************************************************/
+/*
+ * SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
 
 #include "dolphintabwidget.h"
 
 #include "dolphin_generalsettings.h"
 #include "dolphintabbar.h"
-#include "dolphintabpage.h"
 #include "dolphinviewcontainer.h"
 
 #include <KConfigGroup>
-#include <KRun>
 #include <KShell>
 #include <kio/global.h>
+#include <KIO/CommandLauncherJob>
+#include <KAcceleratorManager>
 
 #include <QApplication>
 #include <QDropEvent>
 
-DolphinTabWidget::DolphinTabWidget(QWidget* parent) :
+DolphinTabWidget::DolphinTabWidget(DolphinNavigatorsWidgetAction *navigatorsWidget, QWidget* parent) :
     QTabWidget(parent),
-    m_placesSelectorVisible(true),
-    m_lastViewedTab(0)
+    m_lastViewedTab(nullptr),
+    m_navigatorsWidget{navigatorsWidget}
 {
+    KAcceleratorManager::setNoAccel(this);
+
     connect(this, &DolphinTabWidget::tabCloseRequested,
             this, QOverload<int>::of(&DolphinTabWidget::closeTab));
     connect(this, &DolphinTabWidget::currentChanged,
@@ -98,15 +87,8 @@ void DolphinTabWidget::readProperties(const KConfigGroup& group)
         if (i >= count()) {
             openNewActivatedTab();
         }
-        if (group.hasKey("Tab Data " % QString::number(i))) {
-            // Tab state created with Dolphin > 4.14.x
-            const QByteArray state = group.readEntry("Tab Data " % QString::number(i), QByteArray());
-            tabPageAt(i)->restoreState(state);
-        } else {
-            // Tab state created with Dolphin <= 4.14.x
-            const QByteArray state = group.readEntry("Tab " % QString::number(i), QByteArray());
-            tabPageAt(i)->restoreStateV1(state);
-        }
+        const QByteArray state = group.readEntry("Tab Data " % QString::number(i), QByteArray());
+        tabPageAt(i)->restoreState(state);
     }
 
     const int index = group.readEntry("Active Tab Index", 0);
@@ -130,13 +112,28 @@ void DolphinTabWidget::refreshViews()
     }
 }
 
+bool DolphinTabWidget::isUrlOpen(const QUrl &url) const
+{
+    return indexByUrl(url).first >= 0;
+}
+
+bool DolphinTabWidget::isUrlOrParentOpen(const QUrl &url) const
+{
+    return indexByUrl(url, ReturnIndexForOpenedParentAlso).first >= 0;
+}
+
 void DolphinTabWidget::openNewActivatedTab()
 {
+    std::unique_ptr<DolphinUrlNavigator::VisualState> oldNavigatorState;
+    if (currentTabPage()->primaryViewActive() || !m_navigatorsWidget->secondaryUrlNavigator()) {
+        oldNavigatorState = m_navigatorsWidget->primaryUrlNavigator()->visualState();
+    } else {
+        oldNavigatorState = m_navigatorsWidget->secondaryUrlNavigator()->visualState();
+    }
+
     const DolphinViewContainer* oldActiveViewContainer = currentTabPage()->activeViewContainer();
     Q_ASSERT(oldActiveViewContainer);
 
-    const bool isUrlEditable = oldActiveViewContainer->urlNavigator()->isUrlEditable();
-
     openNewActivatedTab(oldActiveViewContainer->url());
 
     DolphinViewContainer* newActiveViewContainer = currentTabPage()->activeViewContainer();
@@ -144,7 +141,7 @@ void DolphinTabWidget::openNewActivatedTab()
 
     // The URL navigator of the new tab should have the same editable state
     // as the current tab
-    newActiveViewContainer->urlNavigator()->setUrlEditable(isUrlEditable);
+    newActiveViewContainer->urlNavigator()->setVisualState(*oldNavigatorState.get());
 
     // Always focus the new tab's view
     newActiveViewContainer->view()->setFocus();
@@ -153,25 +150,43 @@ void DolphinTabWidget::openNewActivatedTab()
 void DolphinTabWidget::openNewActivatedTab(const QUrl& primaryUrl, const QUrl& secondaryUrl)
 {
     openNewTab(primaryUrl, secondaryUrl);
-    setCurrentIndex(count() - 1);
+    if (GeneralSettings::openNewTabAfterLastTab()) {
+        setCurrentIndex(count() - 1);
+    } else {
+        setCurrentIndex(currentIndex() + 1);
+    }
 }
 
-void DolphinTabWidget::openNewTab(const QUrl& primaryUrl, const QUrl& secondaryUrl, TabPlacement tabPlacement)
+void DolphinTabWidget::openNewTab(const QUrl& primaryUrl, const QUrl& secondaryUrl, DolphinTabWidget::NewTabPosition position)
 {
     QWidget* focusWidget = QApplication::focusWidget();
 
     DolphinTabPage* tabPage = new DolphinTabPage(primaryUrl, secondaryUrl, this);
     tabPage->setActive(false);
-    tabPage->setPlacesSelectorVisible(m_placesSelectorVisible);
     connect(tabPage, &DolphinTabPage::activeViewChanged,
             this, &DolphinTabWidget::activeViewChanged);
     connect(tabPage, &DolphinTabPage::activeViewUrlChanged,
             this, &DolphinTabWidget::tabUrlChanged);
+    connect(tabPage->activeViewContainer(), &DolphinViewContainer::captionChanged, this, [this, tabPage]() {
+        const int tabIndex = indexOf(tabPage);
+        Q_ASSERT(tabIndex >= 0);
+        tabBar()->setTabText(tabIndex, tabName(tabPage));
+    });
+
+    if (position == NewTabPosition::FollowSetting) {
+        if (GeneralSettings::openNewTabAfterLastTab()) {
+            position = NewTabPosition::AtEnd;
+        } else {
+            position = NewTabPosition::AfterCurrent;
+        }
+    }
+
     int newTabIndex = -1;
-    if (tabPlacement == AfterCurrentTab) {
+    if (position == NewTabPosition::AfterCurrent || (position == NewTabPosition::FollowSetting && !GeneralSettings::openNewTabAfterLastTab())) {
         newTabIndex = currentIndex() + 1;
     }
-    insertTab(newTabIndex, tabPage, QIcon::fromTheme(KIO::iconNameForUrl(primaryUrl)), tabName(tabPage));
+
+    insertTab(newTabIndex, tabPage, QIcon() /* loaded in tabInserted */, tabName(tabPage));
 
     if (focusWidget) {
         // The DolphinViewContainer grabbed the keyboard focus. As the tab is opened
@@ -180,23 +195,47 @@ void DolphinTabWidget::openNewTab(const QUrl& primaryUrl, const QUrl& secondaryU
     }
 }
 
-void DolphinTabWidget::openDirectories(const QList<QUrl>& dirs, bool splitView)
+void DolphinTabWidget::openDirectories(const QList<QUrl>& dirs, bool splitView, bool skipChildUrls)
 {
     Q_ASSERT(dirs.size() > 0);
 
+    bool somethingWasAlreadyOpen = false;
+
     QList<QUrl>::const_iterator it = dirs.constBegin();
     while (it != dirs.constEnd()) {
         const QUrl& primaryUrl = *(it++);
-        const int index = getIndexByUrl(primaryUrl);
+        const QPair<int, bool> indexInfo = indexByUrl(primaryUrl, skipChildUrls ? ReturnIndexForOpenedParentAlso : ReturnIndexForOpenedUrlOnly);
+        const int index = indexInfo.first;
+        const bool isInPrimaryView = indexInfo.second;
+
+        // When the user asks for a URL that's already open (or it's parent is open if skipChildUrls is set),
+        // activate it instead of opening a new tab
         if (index >= 0) {
-            setCurrentIndex(index);
-            continue;
-        }
-        if (splitView && (it != dirs.constEnd())) {
+            somethingWasAlreadyOpen = true;
+            activateTab(index);
+            const auto tabPage = tabPageAt(index);
+            if (isInPrimaryView) {
+                tabPage->primaryViewContainer()->setActive(true);
+            } else {
+                tabPage->secondaryViewContainer()->setActive(true);
+            }
+            // BUG: 417230
+            // Required for updateViewState() call in openFiles() to work as expected
+            // If there is a selection, updateViewState() calls are effectively a no-op
+            tabPage->activeViewContainer()->view()->clearSelection();
+        } else if (splitView && (it != dirs.constEnd())) {
             const QUrl& secondaryUrl = *(it++);
-            openNewActivatedTab(primaryUrl, secondaryUrl);
+            if (somethingWasAlreadyOpen) {
+                openNewTab(primaryUrl, secondaryUrl);
+            } else {
+                openNewActivatedTab(primaryUrl, secondaryUrl);
+            }
         } else {
-            openNewActivatedTab(primaryUrl);
+            if (somethingWasAlreadyOpen) {
+                openNewTab(primaryUrl);
+            } else {
+                openNewActivatedTab(primaryUrl);
+            }
         }
     }
 }
@@ -209,24 +248,28 @@ void DolphinTabWidget::openFiles(const QList<QUrl>& files, bool splitView)
     // for each directory. If the "split view" option is enabled, two
     // directories are shown inside one tab (see openDirectories()).
     QList<QUrl> dirs;
-    foreach (const QUrl& url, files) {
-        const QUrl dir(url.adjusted(QUrl::RemoveFilename));
+    for (const QUrl& url : files) {
+        const QUrl dir(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
         if (!dirs.contains(dir)) {
             dirs.append(dir);
         }
     }
 
     const int oldTabCount = count();
-    openDirectories(dirs, splitView);
+    openDirectories(dirs, splitView, true);
     const int tabCount = count();
 
     // Select the files. Although the files can be split between several
     // tabs, there is no need to split 'files' accordingly, as
     // the DolphinView will just ignore invalid selections.
-    for (int i = oldTabCount; i < tabCount; ++i) {
+    for (int i = 0; i < tabCount; ++i) {
         DolphinTabPage* tabPage = tabPageAt(i);
         tabPage->markUrlsAsSelected(files);
         tabPage->markUrlAsCurrent(files.first());
+        if (i < oldTabCount) {
+            // Force selection of file if directory was already open, BUG: 417230
+            tabPage->activeViewContainer()->view()->updateViewState();
+        }
     }
 }
 
@@ -247,12 +290,24 @@ void DolphinTabWidget::closeTab(const int index)
     }
 
     DolphinTabPage* tabPage = tabPageAt(index);
-    emit rememberClosedTab(tabPage->activeViewContainer()->url(), tabPage->saveState());
+    Q_EMIT rememberClosedTab(tabPage->activeViewContainer()->url(), tabPage->saveState());
 
     removeTab(index);
     tabPage->deleteLater();
 }
 
+void DolphinTabWidget::activateTab(const int index)
+{
+    if (index < count()) {
+        setCurrentIndex(index);
+    }
+}
+
+void DolphinTabWidget::activateLastTab()
+{
+    setCurrentIndex(count() - 1);
+}
+
 void DolphinTabWidget::activateNextTab()
 {
     const int index = currentIndex() + 1;
@@ -265,23 +320,44 @@ void DolphinTabWidget::activatePrevTab()
     setCurrentIndex(index >= 0 ? index : (count() - 1));
 }
 
-void DolphinTabWidget::slotPlacesPanelVisibilityChanged(bool visible)
+void DolphinTabWidget::restoreClosedTab(const QByteArray& state)
 {
-    // The places-selector from the URL navigator should only be shown
-    // if the places dock is invisible
-    m_placesSelectorVisible = !visible;
+    openNewActivatedTab();
+    currentTabPage()->restoreState(state);
+}
 
-    const int tabCount = count();
-    for (int i = 0; i < tabCount; ++i) {
-        DolphinTabPage* tabPage = tabPageAt(i);
-        tabPage->setPlacesSelectorVisible(m_placesSelectorVisible);
+void DolphinTabWidget::copyToInactiveSplitView()
+{
+    const DolphinTabPage* tabPage = tabPageAt(currentIndex());
+    DolphinViewContainer* activeViewContainer = currentTabPage()->activeViewContainer();
+    if (!tabPage->splitViewEnabled() || activeViewContainer->view()->selectedItems().isEmpty()) {
+        return;
+    }
+
+    if (tabPage->primaryViewActive()) {
+        // copy from left panel to right
+        activeViewContainer->view()->copySelectedItems(activeViewContainer->view()->selectedItems(), tabPage->secondaryViewContainer()->url());
+    } else {
+        // copy from right panel to left
+        activeViewContainer->view()->copySelectedItems(activeViewContainer->view()->selectedItems(), tabPage->primaryViewContainer()->url());
     }
 }
 
-void DolphinTabWidget::restoreClosedTab(const QByteArray& state)
+void DolphinTabWidget::moveToInactiveSplitView()
 {
-    openNewActivatedTab();
-    currentTabPage()->restoreState(state);
+    const DolphinTabPage* tabPage = tabPageAt(currentIndex());
+    DolphinViewContainer* activeViewContainer = currentTabPage()->activeViewContainer();
+    if (!tabPage->splitViewEnabled() || activeViewContainer->view()->selectedItems().isEmpty()) {
+        return;
+    }
+
+    if (tabPage->primaryViewActive()) {
+        // move from left panel to right
+        activeViewContainer->view()->moveSelectedItems(activeViewContainer->view()->selectedItems(), tabPage->secondaryViewContainer()->url());
+    } else {
+        // move from right panel to left
+        activeViewContainer->view()->moveSelectedItems(activeViewContainer->view()->selectedItems(), tabPage->primaryViewContainer()->url());
+    }
 }
 
 void DolphinTabWidget::detachTab(int index)
@@ -298,8 +374,9 @@ void DolphinTabWidget::detachTab(int index)
     }
     args << QStringLiteral("--new-window");
 
-    const QString command = QStringLiteral("dolphin %1").arg(KShell::joinArgs(args));
-    KRun::runCommand(command, this);
+    KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob("dolphin", args, this);
+    job->setDesktopName(QStringLiteral("org.kde.dolphin"));
+    job->start();
 
     closeTab(index);
 }
@@ -324,27 +401,43 @@ void DolphinTabWidget::tabUrlChanged(const QUrl& url)
     const int index = indexOf(qobject_cast<QWidget*>(sender()));
     if (index >= 0) {
         tabBar()->setTabText(index, tabName(tabPageAt(index)));
-        tabBar()->setTabIcon(index, QIcon::fromTheme(KIO::iconNameForUrl(url)));
+        tabBar()->setTabToolTip(index, url.toDisplayString(QUrl::PreferLocalFile));
+        if (tabBar()->isVisible()) {
+            // ensure the path url ends with a slash to have proper folder icon for remote folders
+            const QUrl pathUrl = QUrl(url.adjusted(QUrl::StripTrailingSlash).toString(QUrl::FullyEncoded).append("/"));
+            tabBar()->setTabIcon(index, QIcon::fromTheme(KIO::iconNameForUrl(pathUrl)));
+        } else {
+            // Mark as dirty, actually load once the tab bar actually gets shown
+            tabBar()->setTabIcon(index, QIcon());
+        }
 
         // Emit the currentUrlChanged signal if the url of the current tab has been changed.
         if (index == currentIndex()) {
-            emit currentUrlChanged(url);
+            Q_EMIT currentUrlChanged(url);
         }
     }
 }
 
 void DolphinTabWidget::currentTabChanged(int index)
 {
-    // last-viewed tab deactivation
-    if (DolphinTabPage* tabPage = tabPageAt(m_lastViewedTab)) {
-        tabPage->setActive(false);
+    DolphinTabPage *tabPage = tabPageAt(index);
+    if (tabPage == m_lastViewedTab) {
+        return;
+    }
+    if (m_lastViewedTab) {
+        m_lastViewedTab->disconnectNavigators();
+        m_lastViewedTab->setActive(false);
+    }
+    if (tabPage->splitViewEnabled() && !m_navigatorsWidget->secondaryUrlNavigator()) {
+        m_navigatorsWidget->createSecondaryUrlNavigator();
     }
-    DolphinTabPage* tabPage = tabPageAt(index);
     DolphinViewContainer* viewContainer = tabPage->activeViewContainer();
-    emit activeViewChanged(viewContainer);
-    emit currentUrlChanged(viewContainer->url());
+    Q_EMIT activeViewChanged(viewContainer);
+    Q_EMIT currentUrlChanged(viewContainer->url());
     tabPage->setActive(true);
-    m_lastViewedTab = index;
+    tabPage->connectNavigators(m_navigatorsWidget);
+    m_navigatorsWidget->setSecondaryNavigatorVisible(tabPage->splitViewEnabled());
+    m_lastViewedTab = tabPage;
 }
 
 void DolphinTabWidget::tabInserted(int index)
@@ -352,10 +445,23 @@ void DolphinTabWidget::tabInserted(int index)
     QTabWidget::tabInserted(index);
 
     if (count() > 1) {
+        // Resolve all pending tab icons
+        for (int i = 0; i < count(); ++i) {
+            const QUrl url = tabPageAt(i)->activeViewContainer()->url();
+            if (tabBar()->tabIcon(i).isNull()) {
+                // ensure the path url ends with a slash to have proper folder icon for remote folders
+                const QUrl pathUrl = QUrl(url.adjusted(QUrl::StripTrailingSlash).toString(QUrl::FullyEncoded).append("/"));
+                tabBar()->setTabIcon(i, QIcon::fromTheme(KIO::iconNameForUrl(pathUrl)));
+            }
+            if (tabBar()->tabToolTip(i).isEmpty()) {
+                tabBar()->setTabToolTip(index, url.toDisplayString(QUrl::PreferLocalFile));
+            }
+        }
+
         tabBar()->show();
     }
 
-    emit tabCountChanged(count());
+    Q_EMIT tabCountChanged(count());
 }
 
 void DolphinTabWidget::tabRemoved(int index)
@@ -368,7 +474,7 @@ void DolphinTabWidget::tabRemoved(int index)
         tabBar()->hide();
     }
 
-    emit tabCountChanged(count());
+    Q_EMIT tabCountChanged(count());
 }
 
 QString DolphinTabWidget::tabName(DolphinTabPage* tabPage) const
@@ -382,16 +488,29 @@ QString DolphinTabWidget::tabName(DolphinTabPage* tabPage) const
     return name.replace('&', QLatin1String("&&"));
 }
 
-int DolphinTabWidget::getIndexByUrl(const QUrl& url) const
+QPair<int, bool> DolphinTabWidget::indexByUrl(const QUrl& url, ChildUrlBehavior childUrlBehavior) const
 {
-    for (int i = 0; i < count(); i++) {
-        // Conversion to display string is necessary to deal with the '~' alias.
-        // i.e. to acknowledge that ~/ is equivalent to /home/user/
-        const QUrl tabUrl = tabPageAt(i)->activeViewContainer()->url();
-        if (url == tabUrl ||
-            url.toDisplayString(QUrl::StripTrailingSlash) == tabUrl.toDisplayString(QUrl::StripTrailingSlash)) {
-            return i;
+    int i = currentIndex();
+    if (i < 0) {
+        return qMakePair(-1, false);
+    }
+    // loop over the tabs starting from the current one
+    do {
+        const auto tabPage = tabPageAt(i);
+        if (tabPage->primaryViewContainer()->url() == url ||
+                (childUrlBehavior == ReturnIndexForOpenedParentAlso && tabPage->primaryViewContainer()->url().isParentOf(url))) {
+            return qMakePair(i, true);
         }
+
+        if (tabPage->splitViewEnabled() &&
+                (url == tabPage->secondaryViewContainer()->url() ||
+                 (childUrlBehavior == ReturnIndexForOpenedParentAlso && tabPage->secondaryViewContainer()->url().isParentOf(url)))) {
+            return qMakePair(i, false);
+        }
+
+        i = (i + 1) % count();
     }
-    return -1;
+    while (i != currentIndex());
+
+    return qMakePair(-1, false);
 }