mirror of
https://invent.kde.org/system/dolphin
synced 2024-09-12 12:54:29 +00:00
530 lines
19 KiB
C++
530 lines
19 KiB
C++
/*
|
|
* SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
|
|
* SPDX-FileCopyrightText: 2020 Felix Ernst <fe.a.ernst@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "dolphintabpage.h"
|
|
|
|
#include "dolphin_generalsettings.h"
|
|
#include "dolphinviewcontainer.h"
|
|
|
|
#include <QGridLayout>
|
|
#include <QStyle>
|
|
#include <QVariantAnimation>
|
|
|
|
DolphinTabPage::DolphinTabPage(const QUrl &primaryUrl, const QUrl &secondaryUrl, QWidget *parent)
|
|
: QWidget(parent)
|
|
, m_expandingContainer{nullptr}
|
|
, m_primaryViewActive(true)
|
|
, m_splitViewEnabled(false)
|
|
, m_active(true)
|
|
{
|
|
QGridLayout *layout = new QGridLayout(this);
|
|
layout->setSpacing(0);
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
m_splitter = new DolphinTabPageSplitter(Qt::Horizontal, this);
|
|
m_splitter->setChildrenCollapsible(false);
|
|
connect(m_splitter, &QSplitter::splitterMoved, this, &DolphinTabPage::splitterMoved);
|
|
layout->addWidget(m_splitter, 1, 0);
|
|
layout->setRowStretch(1, 1);
|
|
|
|
// Create a new primary view
|
|
m_primaryViewContainer = createViewContainer(primaryUrl);
|
|
connect(m_primaryViewContainer->view(), &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
|
|
connect(m_primaryViewContainer->view(), &DolphinView::redirection, this, &DolphinTabPage::slotViewUrlRedirection);
|
|
|
|
m_splitter->addWidget(m_primaryViewContainer);
|
|
m_primaryViewContainer->show();
|
|
|
|
if (secondaryUrl.isValid() || GeneralSettings::splitView()) {
|
|
// Provide a secondary view, if the given secondary url is valid or if the
|
|
// startup settings are set this way (use the url of the primary view).
|
|
m_splitViewEnabled = true;
|
|
const QUrl &url = secondaryUrl.isValid() ? secondaryUrl : primaryUrl;
|
|
m_secondaryViewContainer = createViewContainer(url);
|
|
m_splitter->addWidget(m_secondaryViewContainer);
|
|
m_secondaryViewContainer->show();
|
|
}
|
|
|
|
m_primaryViewContainer->setActive(true);
|
|
}
|
|
|
|
bool DolphinTabPage::primaryViewActive() const
|
|
{
|
|
return m_primaryViewActive;
|
|
}
|
|
|
|
bool DolphinTabPage::splitViewEnabled() const
|
|
{
|
|
return m_splitViewEnabled;
|
|
}
|
|
|
|
void DolphinTabPage::setSplitViewEnabled(bool enabled, Animated animated, const QUrl &secondaryUrl)
|
|
{
|
|
if (m_splitViewEnabled != enabled) {
|
|
m_splitViewEnabled = enabled;
|
|
if (animated == WithAnimation
|
|
&& (style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) < 1 || GlobalConfig::animationDurationFactor() <= 0.0)) {
|
|
animated = WithoutAnimation;
|
|
}
|
|
if (m_expandViewAnimation) {
|
|
m_expandViewAnimation->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped.
|
|
if (animated == WithoutAnimation) {
|
|
slotAnimationFinished();
|
|
}
|
|
}
|
|
|
|
if (enabled) {
|
|
QList<int> splitterSizes = m_splitter->sizes();
|
|
const QUrl &url = (secondaryUrl.isEmpty()) ? m_primaryViewContainer->url() : secondaryUrl;
|
|
m_secondaryViewContainer = createViewContainer(url);
|
|
|
|
auto secondaryNavigator = m_navigatorsWidget->secondaryUrlNavigator();
|
|
if (!secondaryNavigator) {
|
|
m_navigatorsWidget->createSecondaryUrlNavigator();
|
|
secondaryNavigator = m_navigatorsWidget->secondaryUrlNavigator();
|
|
}
|
|
m_secondaryViewContainer->connectUrlNavigator(secondaryNavigator);
|
|
m_navigatorsWidget->setSecondaryNavigatorVisible(true);
|
|
m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer, m_secondaryViewContainer);
|
|
|
|
m_splitter->addWidget(m_secondaryViewContainer);
|
|
m_secondaryViewContainer->setActive(true);
|
|
|
|
if (animated == WithAnimation) {
|
|
m_secondaryViewContainer->setMinimumWidth(1);
|
|
splitterSizes.append(1);
|
|
m_splitter->setSizes(splitterSizes);
|
|
startExpandViewAnimation(m_secondaryViewContainer);
|
|
}
|
|
m_secondaryViewContainer->show();
|
|
} else {
|
|
m_navigatorsWidget->setSecondaryNavigatorVisible(false);
|
|
m_secondaryViewContainer->disconnectUrlNavigator();
|
|
|
|
DolphinViewContainer *view;
|
|
if (GeneralSettings::closeActiveSplitView()) {
|
|
view = activeViewContainer();
|
|
if (m_primaryViewActive) {
|
|
m_primaryViewContainer->disconnectUrlNavigator();
|
|
m_secondaryViewContainer->connectUrlNavigator(m_navigatorsWidget->primaryUrlNavigator());
|
|
|
|
// If the primary view is active, we have to swap the pointers
|
|
// because the secondary view will be the new primary view.
|
|
std::swap(m_primaryViewContainer, m_secondaryViewContainer);
|
|
m_primaryViewActive = false;
|
|
}
|
|
} else {
|
|
view = m_primaryViewActive ? m_secondaryViewContainer : m_primaryViewContainer;
|
|
if (!m_primaryViewActive) {
|
|
m_primaryViewContainer->disconnectUrlNavigator();
|
|
m_secondaryViewContainer->connectUrlNavigator(m_navigatorsWidget->primaryUrlNavigator());
|
|
|
|
// If the secondary view is active, we have to swap the pointers
|
|
// because the secondary view will be the new primary view.
|
|
std::swap(m_primaryViewContainer, m_secondaryViewContainer);
|
|
m_primaryViewActive = true;
|
|
}
|
|
}
|
|
m_primaryViewContainer->setActive(true);
|
|
m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer, nullptr);
|
|
|
|
if (animated == WithoutAnimation) {
|
|
view->close();
|
|
view->deleteLater();
|
|
} else {
|
|
// Kill it but keep it as a zombie for the closing animation.
|
|
m_secondaryViewContainer = nullptr;
|
|
view->blockSignals(true);
|
|
view->view()->blockSignals(true);
|
|
view->setDisabled(true);
|
|
startExpandViewAnimation(m_primaryViewContainer);
|
|
}
|
|
|
|
m_primaryViewContainer->slotSplitTabDisabled();
|
|
}
|
|
}
|
|
}
|
|
|
|
DolphinViewContainer *DolphinTabPage::primaryViewContainer() const
|
|
{
|
|
return m_primaryViewContainer;
|
|
}
|
|
|
|
DolphinViewContainer *DolphinTabPage::secondaryViewContainer() const
|
|
{
|
|
return m_secondaryViewContainer;
|
|
}
|
|
|
|
DolphinViewContainer *DolphinTabPage::activeViewContainer() const
|
|
{
|
|
return m_primaryViewActive ? m_primaryViewContainer : m_secondaryViewContainer;
|
|
}
|
|
|
|
KFileItemList DolphinTabPage::selectedItems() const
|
|
{
|
|
KFileItemList items = m_primaryViewContainer->view()->selectedItems();
|
|
if (m_splitViewEnabled) {
|
|
items += m_secondaryViewContainer->view()->selectedItems();
|
|
}
|
|
return items;
|
|
}
|
|
|
|
int DolphinTabPage::selectedItemsCount() const
|
|
{
|
|
int selectedItemsCount = m_primaryViewContainer->view()->selectedItemsCount();
|
|
if (m_splitViewEnabled) {
|
|
selectedItemsCount += m_secondaryViewContainer->view()->selectedItemsCount();
|
|
}
|
|
return selectedItemsCount;
|
|
}
|
|
|
|
void DolphinTabPage::connectNavigators(DolphinNavigatorsWidgetAction *navigatorsWidget)
|
|
{
|
|
insertNavigatorsWidget(navigatorsWidget);
|
|
m_navigatorsWidget = navigatorsWidget;
|
|
auto primaryNavigator = navigatorsWidget->primaryUrlNavigator();
|
|
m_primaryViewContainer->connectUrlNavigator(primaryNavigator);
|
|
if (m_splitViewEnabled) {
|
|
auto secondaryNavigator = navigatorsWidget->secondaryUrlNavigator();
|
|
m_secondaryViewContainer->connectUrlNavigator(secondaryNavigator);
|
|
}
|
|
m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer, m_secondaryViewContainer);
|
|
}
|
|
|
|
void DolphinTabPage::disconnectNavigators()
|
|
{
|
|
m_navigatorsWidget = nullptr;
|
|
m_primaryViewContainer->disconnectUrlNavigator();
|
|
if (m_splitViewEnabled) {
|
|
m_secondaryViewContainer->disconnectUrlNavigator();
|
|
}
|
|
}
|
|
|
|
void DolphinTabPage::insertNavigatorsWidget(DolphinNavigatorsWidgetAction *navigatorsWidget)
|
|
{
|
|
QGridLayout *gridLayout = static_cast<QGridLayout *>(layout());
|
|
if (navigatorsWidget->isInToolbar()) {
|
|
gridLayout->setRowMinimumHeight(0, 0);
|
|
} else {
|
|
// We set a row minimum height, so the height does not visibly change whenever
|
|
// navigatorsWidget is inserted which happens every time the current tab is changed.
|
|
gridLayout->setRowMinimumHeight(0, navigatorsWidget->primaryUrlNavigator()->height());
|
|
gridLayout->addWidget(navigatorsWidget->requestWidget(this), 0, 0);
|
|
}
|
|
}
|
|
|
|
void DolphinTabPage::markUrlsAsSelected(const QList<QUrl> &urls)
|
|
{
|
|
m_primaryViewContainer->view()->markUrlsAsSelected(urls);
|
|
if (m_splitViewEnabled) {
|
|
m_secondaryViewContainer->view()->markUrlsAsSelected(urls);
|
|
}
|
|
}
|
|
|
|
void DolphinTabPage::markUrlAsCurrent(const QUrl &url)
|
|
{
|
|
m_primaryViewContainer->view()->markUrlAsCurrent(url);
|
|
if (m_splitViewEnabled) {
|
|
m_secondaryViewContainer->view()->markUrlAsCurrent(url);
|
|
}
|
|
}
|
|
|
|
void DolphinTabPage::refreshViews()
|
|
{
|
|
m_primaryViewContainer->readSettings();
|
|
if (m_splitViewEnabled) {
|
|
m_secondaryViewContainer->readSettings();
|
|
}
|
|
}
|
|
|
|
QByteArray DolphinTabPage::saveState() const
|
|
{
|
|
QByteArray state;
|
|
QDataStream stream(&state, QIODevice::WriteOnly);
|
|
|
|
stream << quint32(2); // Tab state version
|
|
|
|
stream << m_splitViewEnabled;
|
|
|
|
stream << m_primaryViewContainer->url();
|
|
stream << m_primaryViewContainer->urlNavigatorInternalWithHistory()->isUrlEditable();
|
|
m_primaryViewContainer->view()->saveState(stream);
|
|
|
|
if (m_splitViewEnabled) {
|
|
stream << m_secondaryViewContainer->url();
|
|
stream << m_secondaryViewContainer->urlNavigatorInternalWithHistory()->isUrlEditable();
|
|
m_secondaryViewContainer->view()->saveState(stream);
|
|
}
|
|
|
|
stream << m_primaryViewActive;
|
|
stream << m_splitter->saveState();
|
|
|
|
return state;
|
|
}
|
|
|
|
void DolphinTabPage::restoreState(const QByteArray &state)
|
|
{
|
|
if (state.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QByteArray sd = state;
|
|
QDataStream stream(&sd, QIODevice::ReadOnly);
|
|
|
|
// Read the version number of the tab state and check if the version is supported.
|
|
quint32 version = 0;
|
|
stream >> version;
|
|
if (version != 2) {
|
|
// The version of the tab state isn't supported, we can't restore it.
|
|
return;
|
|
}
|
|
|
|
bool isSplitViewEnabled = false;
|
|
stream >> isSplitViewEnabled;
|
|
setSplitViewEnabled(isSplitViewEnabled, WithoutAnimation);
|
|
|
|
QUrl primaryUrl;
|
|
stream >> primaryUrl;
|
|
m_primaryViewContainer->setUrl(primaryUrl);
|
|
bool primaryUrlEditable;
|
|
stream >> primaryUrlEditable;
|
|
m_primaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable);
|
|
m_primaryViewContainer->view()->restoreState(stream);
|
|
|
|
if (isSplitViewEnabled) {
|
|
QUrl secondaryUrl;
|
|
stream >> secondaryUrl;
|
|
m_secondaryViewContainer->setUrl(secondaryUrl);
|
|
bool secondaryUrlEditable;
|
|
stream >> secondaryUrlEditable;
|
|
m_secondaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable);
|
|
m_secondaryViewContainer->view()->restoreState(stream);
|
|
}
|
|
|
|
stream >> m_primaryViewActive;
|
|
if (m_primaryViewActive) {
|
|
m_primaryViewContainer->setActive(true);
|
|
} else {
|
|
Q_ASSERT(m_splitViewEnabled);
|
|
m_secondaryViewContainer->setActive(true);
|
|
}
|
|
|
|
QByteArray splitterState;
|
|
stream >> splitterState;
|
|
m_splitter->restoreState(splitterState);
|
|
}
|
|
|
|
void DolphinTabPage::setActive(bool active)
|
|
{
|
|
if (active) {
|
|
m_active = active;
|
|
} else {
|
|
// we should bypass changing active view in split mode
|
|
m_active = !m_splitViewEnabled;
|
|
}
|
|
// we want view to fire activated when goes from false to true
|
|
activeViewContainer()->setActive(active);
|
|
}
|
|
|
|
void DolphinTabPage::slotAnimationFinished()
|
|
{
|
|
for (int i = 0; i < m_splitter->count(); ++i) {
|
|
QWidget *viewContainer = m_splitter->widget(i);
|
|
if (viewContainer != m_primaryViewContainer && viewContainer != m_secondaryViewContainer) {
|
|
viewContainer->close();
|
|
viewContainer->deleteLater();
|
|
}
|
|
}
|
|
for (int i = 0; i < m_splitter->count(); ++i) {
|
|
QWidget *viewContainer = m_splitter->widget(i);
|
|
viewContainer->setMinimumWidth(viewContainer->minimumSizeHint().width());
|
|
}
|
|
m_expandingContainer = nullptr;
|
|
}
|
|
|
|
void DolphinTabPage::slotAnimationValueChanged(const QVariant &value)
|
|
{
|
|
Q_CHECK_PTR(m_expandingContainer);
|
|
const int indexOfExpandingContainer = m_splitter->indexOf(m_expandingContainer);
|
|
int indexOfNonExpandingContainer = -1;
|
|
if (m_expandingContainer == m_primaryViewContainer) {
|
|
indexOfNonExpandingContainer = m_splitter->indexOf(m_secondaryViewContainer);
|
|
} else {
|
|
indexOfNonExpandingContainer = m_splitter->indexOf(m_primaryViewContainer);
|
|
}
|
|
std::vector<QWidget *> widgetsToRemove;
|
|
const QList<int> oldSplitterSizes = m_splitter->sizes();
|
|
QList<int> newSplitterSizes{oldSplitterSizes};
|
|
int expansionWidthNeeded = value.toInt() - oldSplitterSizes.at(indexOfExpandingContainer);
|
|
|
|
// Reduce the size of the other widgets to make space for the expandingContainer.
|
|
for (int i = m_splitter->count() - 1; i >= 0; --i) {
|
|
if (m_splitter->widget(i) == m_primaryViewContainer || m_splitter->widget(i) == m_secondaryViewContainer) {
|
|
continue;
|
|
}
|
|
newSplitterSizes[i] = oldSplitterSizes.at(i) - expansionWidthNeeded;
|
|
expansionWidthNeeded = 0;
|
|
if (indexOfNonExpandingContainer != -1) {
|
|
// Make sure every zombie container is at least slightly reduced in size
|
|
// so it doesn't seem like they are here to stay.
|
|
newSplitterSizes[i]--;
|
|
newSplitterSizes[indexOfNonExpandingContainer]++;
|
|
}
|
|
if (newSplitterSizes.at(i) <= 0) {
|
|
expansionWidthNeeded -= newSplitterSizes.at(i);
|
|
newSplitterSizes[i] = 0;
|
|
widgetsToRemove.emplace_back(m_splitter->widget(i));
|
|
}
|
|
}
|
|
if (expansionWidthNeeded > 1 && indexOfNonExpandingContainer != -1) {
|
|
Q_ASSERT(m_splitViewEnabled);
|
|
newSplitterSizes[indexOfNonExpandingContainer] -= expansionWidthNeeded;
|
|
}
|
|
newSplitterSizes[indexOfExpandingContainer] = value.toInt();
|
|
m_splitter->setSizes(newSplitterSizes);
|
|
while (!widgetsToRemove.empty()) {
|
|
widgetsToRemove.back()->close();
|
|
widgetsToRemove.back()->deleteLater();
|
|
widgetsToRemove.pop_back();
|
|
}
|
|
}
|
|
|
|
void DolphinTabPage::slotViewActivated()
|
|
{
|
|
const DolphinView *oldActiveView = activeViewContainer()->view();
|
|
|
|
// Set the view, which was active before, to inactive
|
|
// and update the active view type, if tab is active
|
|
if (m_active) {
|
|
if (m_splitViewEnabled) {
|
|
activeViewContainer()->setActive(false);
|
|
m_primaryViewActive = !m_primaryViewActive;
|
|
} else {
|
|
m_primaryViewActive = true;
|
|
if (m_secondaryViewContainer) {
|
|
m_secondaryViewContainer->setActive(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
const DolphinView *newActiveView = activeViewContainer()->view();
|
|
|
|
if (newActiveView == oldActiveView) {
|
|
return;
|
|
}
|
|
|
|
disconnect(oldActiveView, &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
|
|
disconnect(oldActiveView, &DolphinView::redirection, this, &DolphinTabPage::slotViewUrlRedirection);
|
|
connect(newActiveView, &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
|
|
connect(newActiveView, &DolphinView::redirection, this, &DolphinTabPage::slotViewUrlRedirection);
|
|
Q_EMIT activeViewChanged(activeViewContainer());
|
|
Q_EMIT activeViewUrlChanged(activeViewContainer()->url());
|
|
}
|
|
|
|
void DolphinTabPage::slotViewUrlRedirection(const QUrl &oldUrl, const QUrl &newUrl)
|
|
{
|
|
Q_UNUSED(oldUrl)
|
|
|
|
Q_EMIT activeViewUrlChanged(newUrl);
|
|
}
|
|
|
|
void DolphinTabPage::switchActiveView()
|
|
{
|
|
if (!m_splitViewEnabled) {
|
|
return;
|
|
}
|
|
if (m_primaryViewActive) {
|
|
m_secondaryViewContainer->setActive(true);
|
|
} else {
|
|
m_primaryViewContainer->setActive(true);
|
|
}
|
|
}
|
|
|
|
DolphinViewContainer *DolphinTabPage::createViewContainer(const QUrl &url) const
|
|
{
|
|
DolphinViewContainer *container = new DolphinViewContainer(url, m_splitter);
|
|
container->setActive(false);
|
|
|
|
const DolphinView *view = container->view();
|
|
connect(view, &DolphinView::activated, this, &DolphinTabPage::slotViewActivated);
|
|
|
|
connect(view, &DolphinView::toggleActiveViewRequested, this, &DolphinTabPage::switchActiveView);
|
|
|
|
return container;
|
|
}
|
|
|
|
void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer *expandingContainer)
|
|
{
|
|
Q_CHECK_PTR(expandingContainer);
|
|
Q_ASSERT(expandingContainer == m_primaryViewContainer || expandingContainer == m_secondaryViewContainer);
|
|
m_expandingContainer = expandingContainer;
|
|
|
|
m_expandViewAnimation = new QVariantAnimation(m_splitter);
|
|
m_expandViewAnimation->setDuration(2 * style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) * GlobalConfig::animationDurationFactor());
|
|
for (int i = 0; i < m_splitter->count(); ++i) {
|
|
m_splitter->widget(i)->setMinimumWidth(1);
|
|
}
|
|
connect(m_expandViewAnimation, &QAbstractAnimation::finished, this, &DolphinTabPage::slotAnimationFinished);
|
|
connect(m_expandViewAnimation, &QVariantAnimation::valueChanged, this, &DolphinTabPage::slotAnimationValueChanged);
|
|
|
|
m_expandViewAnimation->setStartValue(expandingContainer->width());
|
|
if (m_splitViewEnabled) { // A new viewContainer is being opened.
|
|
m_expandViewAnimation->setEndValue(m_splitter->width() / 2);
|
|
m_expandViewAnimation->setEasingCurve(QEasingCurve::OutCubic);
|
|
} else { // A viewContainer is being closed.
|
|
m_expandViewAnimation->setEndValue(m_splitter->width());
|
|
m_expandViewAnimation->setEasingCurve(QEasingCurve::InCubic);
|
|
}
|
|
m_expandViewAnimation->start(QAbstractAnimation::DeleteWhenStopped);
|
|
}
|
|
|
|
DolphinTabPageSplitterHandle::DolphinTabPageSplitterHandle(Qt::Orientation orientation, QSplitter *parent)
|
|
: QSplitterHandle(orientation, parent)
|
|
, m_mouseReleaseWasReceived(false)
|
|
{
|
|
}
|
|
|
|
bool DolphinTabPageSplitterHandle::event(QEvent *event)
|
|
{
|
|
switch (event->type()) {
|
|
case QEvent::MouseButtonPress:
|
|
m_mouseReleaseWasReceived = false;
|
|
break;
|
|
case QEvent::MouseButtonRelease:
|
|
if (m_mouseReleaseWasReceived) {
|
|
resetSplitterSizes();
|
|
}
|
|
m_mouseReleaseWasReceived = !m_mouseReleaseWasReceived;
|
|
break;
|
|
case QEvent::MouseButtonDblClick:
|
|
m_mouseReleaseWasReceived = false;
|
|
resetSplitterSizes();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return QSplitterHandle::event(event);
|
|
}
|
|
|
|
void DolphinTabPageSplitterHandle::resetSplitterSizes()
|
|
{
|
|
QList<int> splitterSizes = splitter()->sizes();
|
|
std::fill(splitterSizes.begin(), splitterSizes.end(), 0);
|
|
splitter()->setSizes(splitterSizes);
|
|
}
|
|
|
|
DolphinTabPageSplitter::DolphinTabPageSplitter(Qt::Orientation orientation, QWidget *parent)
|
|
: QSplitter(orientation, parent)
|
|
{
|
|
}
|
|
|
|
QSplitterHandle *DolphinTabPageSplitter::createHandle()
|
|
{
|
|
return new DolphinTabPageSplitterHandle(orientation(), this);
|
|
}
|