-/***************************************************************************
- * 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, static_cast<void (DolphinTabWidget::*)(int)>(&DolphinTabWidget::closeTab));
+ this, QOverload<int>::of(&DolphinTabWidget::closeTab));
connect(this, &DolphinTabWidget::currentChanged,
this, &DolphinTabWidget::currentTabChanged);
DolphinTabBar* tabBar = new DolphinTabBar(this);
connect(tabBar, &DolphinTabBar::openNewActivatedTab,
- this, static_cast<void (DolphinTabWidget::*)(int)>(&DolphinTabWidget::openNewActivatedTab));
+ this, QOverload<int>::of(&DolphinTabWidget::openNewActivatedTab));
connect(tabBar, &DolphinTabBar::tabDropEvent,
this, &DolphinTabWidget::tabDropEvent);
connect(tabBar, &DolphinTabBar::tabDetachRequested,
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);
void DolphinTabWidget::refreshViews()
{
+ // Left-elision is better when showing full paths, since you care most
+ // about the current directory which is on the right
+ if (GeneralSettings::showFullPathInTitlebar()) {
+ setElideMode(Qt::ElideLeft);
+ } else {
+ setElideMode(Qt::ElideRight);
+ }
+
const int tabCount = count();
for (int i = 0; i < tabCount; ++i) {
tabBar()->setTabText(i, tabName(tabPageAt(i)));
}
}
+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();
// The URL navigator of the new tab should have the same editable state
// as the current tab
- KUrlNavigator* navigator = newActiveViewContainer->urlNavigator();
- navigator->setUrlEditable(isUrlEditable);
+ newActiveViewContainer->urlNavigator()->setVisualState(*oldNavigatorState.get());
- if (isUrlEditable) {
- // If a new tab is opened and the URL is editable, assure that
- // the user can edit the URL without manually setting the focus
- navigator->setFocus();
- }
+ // Always focus the new tab's view
+ newActiveViewContainer->view()->setFocus();
}
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)
+void DolphinTabWidget::openNewTab(const QUrl& primaryUrl, const QUrl& secondaryUrl, DolphinTabWidget::NewTabPosition position)
{
QWidget* focusWidget = QApplication::focusWidget();
DolphinTabPage* tabPage = new DolphinTabPage(primaryUrl, secondaryUrl, this);
- tabPage->setPlacesSelectorVisible(m_placesSelectorVisible);
+ tabPage->setActive(false);
connect(tabPage, &DolphinTabPage::activeViewChanged,
this, &DolphinTabWidget::activeViewChanged);
connect(tabPage, &DolphinTabPage::activeViewUrlChanged,
this, &DolphinTabWidget::tabUrlChanged);
- addTab(tabPage, QIcon::fromTheme(KIO::iconNameForUrl(primaryUrl)), tabName(tabPage));
+ 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 (position == NewTabPosition::AfterCurrent || (position == NewTabPosition::FollowSetting && !GeneralSettings::openNewTabAfterLastTab())) {
+ newTabIndex = currentIndex() + 1;
+ }
+
+ insertTab(newTabIndex, tabPage, QIcon() /* loaded in tabInserted */, tabName(tabPage));
if (focusWidget) {
// The DolphinViewContainer grabbed the keyboard focus. As the tab is opened
}
}
-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++);
- if (splitView && (it != dirs.constEnd())) {
+ 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) {
+ 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++);
- openNewTab(primaryUrl, secondaryUrl);
+ if (somethingWasAlreadyOpen) {
+ openNewTab(primaryUrl, secondaryUrl);
+ } else {
+ openNewActivatedTab(primaryUrl, secondaryUrl);
+ }
} else {
- openNewTab(primaryUrl);
+ if (somethingWasAlreadyOpen) {
+ openNewTab(primaryUrl);
+ } else {
+ openNewActivatedTab(primaryUrl);
+ }
}
}
}
// 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();
+ }
}
}
Q_ASSERT(index < count());
if (count() < 2) {
- // Never close the last tab.
+ // Close Dolphin when closing the last tab.
+ parentWidget()->close();
return;
}
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;
setCurrentIndex(index >= 0 ? index : (count() - 1));
}
-void DolphinTabWidget::slotPlacesPanelVisibilityChanged(bool visible)
+void DolphinTabWidget::restoreClosedTab(const QByteArray& state)
+{
+ openNewActivatedTab();
+ currentTabPage()->restoreState(state);
+}
+
+void DolphinTabWidget::copyToInactiveSplitView()
{
- // The places-selector from the URL navigator should only be shown
- // if the places dock is invisible
- m_placesSelectorVisible = !visible;
+ const DolphinTabPage* tabPage = tabPageAt(currentIndex());
+ DolphinViewContainer* activeViewContainer = currentTabPage()->activeViewContainer();
+ if (!tabPage->splitViewEnabled() || activeViewContainer->view()->selectedItems().isEmpty()) {
+ return;
+ }
- const int tabCount = count();
- for (int i = 0; i < tabCount; ++i) {
- DolphinTabPage* tabPage = tabPageAt(i);
- tabPage->setPlacesSelectorVisible(m_placesSelectorVisible);
+ 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)
args << tabPage->secondaryViewContainer()->url().url();
args << QStringLiteral("--split");
}
+ 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);
}
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)
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)
tabBar()->hide();
}
- emit tabCountChanged(count());
+ Q_EMIT tabCountChanged(count());
}
QString DolphinTabWidget::tabName(DolphinTabPage* tabPage) const
// and not misinterpreted as a keyboard shortcut in QTabBar::setTabText()
return name.replace('&', QLatin1String("&&"));
}
+
+QPair<int, bool> DolphinTabWidget::indexByUrl(const QUrl& url, ChildUrlBehavior childUrlBehavior) const
+{
+ 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();
+ }
+ while (i != currentIndex());
+
+ return qMakePair(-1, false);
+}