File indexing completed on 2024-04-14 05:38:43

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
0003  * SPDX-FileCopyrightText: 2020 Felix Ernst <felixernst@kde.org>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "dolphintabpage.h"
0009 
0010 #include "dolphin_generalsettings.h"
0011 #include "dolphinviewcontainer.h"
0012 
0013 #include <QGridLayout>
0014 #include <QStyle>
0015 #include <QVariantAnimation>
0016 
0017 DolphinTabPage::DolphinTabPage(const QUrl &primaryUrl, const QUrl &secondaryUrl, QWidget *parent)
0018     : QWidget(parent)
0019     , m_expandingContainer{nullptr}
0020     , m_primaryViewActive(true)
0021     , m_splitViewEnabled(false)
0022     , m_active(true)
0023 {
0024     QGridLayout *layout = new QGridLayout(this);
0025     layout->setSpacing(0);
0026     layout->setContentsMargins(0, 0, 0, 0);
0027 
0028     m_splitter = new DolphinTabPageSplitter(Qt::Horizontal, this);
0029     m_splitter->setChildrenCollapsible(false);
0030     connect(m_splitter, &QSplitter::splitterMoved, this, &DolphinTabPage::splitterMoved);
0031     layout->addWidget(m_splitter, 1, 0);
0032     layout->setRowStretch(1, 1);
0033 
0034     // Create a new primary view
0035     m_primaryViewContainer = createViewContainer(primaryUrl);
0036     connect(m_primaryViewContainer->view(), &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
0037     connect(m_primaryViewContainer->view(), &DolphinView::redirection, this, &DolphinTabPage::slotViewUrlRedirection);
0038 
0039     m_splitter->addWidget(m_primaryViewContainer);
0040     m_primaryViewContainer->show();
0041 
0042     if (secondaryUrl.isValid() || GeneralSettings::splitView()) {
0043         // Provide a secondary view, if the given secondary url is valid or if the
0044         // startup settings are set this way (use the url of the primary view).
0045         m_splitViewEnabled = true;
0046         const QUrl &url = secondaryUrl.isValid() ? secondaryUrl : primaryUrl;
0047         m_secondaryViewContainer = createViewContainer(url);
0048         m_splitter->addWidget(m_secondaryViewContainer);
0049         m_secondaryViewContainer->show();
0050     }
0051 
0052     m_primaryViewContainer->setActive(true);
0053 }
0054 
0055 bool DolphinTabPage::primaryViewActive() const
0056 {
0057     return m_primaryViewActive;
0058 }
0059 
0060 bool DolphinTabPage::splitViewEnabled() const
0061 {
0062     return m_splitViewEnabled;
0063 }
0064 
0065 void DolphinTabPage::setSplitViewEnabled(bool enabled, Animated animated, const QUrl &secondaryUrl)
0066 {
0067     if (m_splitViewEnabled != enabled) {
0068         m_splitViewEnabled = enabled;
0069         if (animated == WithAnimation
0070             && (style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) < 1 || GlobalConfig::animationDurationFactor() <= 0.0)) {
0071             animated = WithoutAnimation;
0072         }
0073         if (m_expandViewAnimation) {
0074             m_expandViewAnimation->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped.
0075             if (animated == WithoutAnimation) {
0076                 slotAnimationFinished();
0077             }
0078         }
0079 
0080         if (enabled) {
0081             QList<int> splitterSizes = m_splitter->sizes();
0082             const QUrl &url = (secondaryUrl.isEmpty()) ? m_primaryViewContainer->url() : secondaryUrl;
0083             m_secondaryViewContainer = createViewContainer(url);
0084 
0085             auto secondaryNavigator = m_navigatorsWidget->secondaryUrlNavigator();
0086             if (!secondaryNavigator) {
0087                 m_navigatorsWidget->createSecondaryUrlNavigator();
0088                 secondaryNavigator = m_navigatorsWidget->secondaryUrlNavigator();
0089             }
0090             m_secondaryViewContainer->connectUrlNavigator(secondaryNavigator);
0091             m_navigatorsWidget->setSecondaryNavigatorVisible(true);
0092             m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer, m_secondaryViewContainer);
0093 
0094             m_splitter->addWidget(m_secondaryViewContainer);
0095             m_secondaryViewContainer->setActive(true);
0096 
0097             if (animated == WithAnimation) {
0098                 m_secondaryViewContainer->setMinimumWidth(1);
0099                 splitterSizes.append(1);
0100                 m_splitter->setSizes(splitterSizes);
0101                 startExpandViewAnimation(m_secondaryViewContainer);
0102             }
0103             m_secondaryViewContainer->show();
0104         } else {
0105             m_navigatorsWidget->setSecondaryNavigatorVisible(false);
0106             m_secondaryViewContainer->disconnectUrlNavigator();
0107 
0108             DolphinViewContainer *view;
0109             if (GeneralSettings::closeActiveSplitView()) {
0110                 view = activeViewContainer();
0111                 if (m_primaryViewActive) {
0112                     m_primaryViewContainer->disconnectUrlNavigator();
0113                     m_secondaryViewContainer->connectUrlNavigator(m_navigatorsWidget->primaryUrlNavigator());
0114 
0115                     // If the primary view is active, we have to swap the pointers
0116                     // because the secondary view will be the new primary view.
0117                     std::swap(m_primaryViewContainer, m_secondaryViewContainer);
0118                     m_primaryViewActive = false;
0119                 }
0120             } else {
0121                 view = m_primaryViewActive ? m_secondaryViewContainer : m_primaryViewContainer;
0122                 if (!m_primaryViewActive) {
0123                     m_primaryViewContainer->disconnectUrlNavigator();
0124                     m_secondaryViewContainer->connectUrlNavigator(m_navigatorsWidget->primaryUrlNavigator());
0125 
0126                     // If the secondary view is active, we have to swap the pointers
0127                     // because the secondary view will be the new primary view.
0128                     std::swap(m_primaryViewContainer, m_secondaryViewContainer);
0129                     m_primaryViewActive = true;
0130                 }
0131             }
0132             m_primaryViewContainer->setActive(true);
0133             m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer, nullptr);
0134 
0135             if (animated == WithoutAnimation) {
0136                 view->close();
0137                 view->deleteLater();
0138             } else {
0139                 // Kill it but keep it as a zombie for the closing animation.
0140                 m_secondaryViewContainer = nullptr;
0141                 view->blockSignals(true);
0142                 view->view()->blockSignals(true);
0143                 view->setDisabled(true);
0144                 startExpandViewAnimation(m_primaryViewContainer);
0145             }
0146 
0147             m_primaryViewContainer->slotSplitTabDisabled();
0148         }
0149     }
0150 }
0151 
0152 DolphinViewContainer *DolphinTabPage::primaryViewContainer() const
0153 {
0154     return m_primaryViewContainer;
0155 }
0156 
0157 DolphinViewContainer *DolphinTabPage::secondaryViewContainer() const
0158 {
0159     return m_secondaryViewContainer;
0160 }
0161 
0162 DolphinViewContainer *DolphinTabPage::activeViewContainer() const
0163 {
0164     return m_primaryViewActive ? m_primaryViewContainer : m_secondaryViewContainer;
0165 }
0166 
0167 DolphinViewContainer *DolphinTabPage::inactiveViewContainer() const
0168 {
0169     if (!splitViewEnabled()) {
0170         return nullptr;
0171     }
0172 
0173     return primaryViewActive() ? secondaryViewContainer() : primaryViewContainer();
0174 }
0175 
0176 KFileItemList DolphinTabPage::selectedItems() const
0177 {
0178     KFileItemList items = m_primaryViewContainer->view()->selectedItems();
0179     if (m_splitViewEnabled) {
0180         items += m_secondaryViewContainer->view()->selectedItems();
0181     }
0182     return items;
0183 }
0184 
0185 int DolphinTabPage::selectedItemsCount() const
0186 {
0187     int selectedItemsCount = m_primaryViewContainer->view()->selectedItemsCount();
0188     if (m_splitViewEnabled) {
0189         selectedItemsCount += m_secondaryViewContainer->view()->selectedItemsCount();
0190     }
0191     return selectedItemsCount;
0192 }
0193 
0194 void DolphinTabPage::connectNavigators(DolphinNavigatorsWidgetAction *navigatorsWidget)
0195 {
0196     insertNavigatorsWidget(navigatorsWidget);
0197     m_navigatorsWidget = navigatorsWidget;
0198     auto primaryNavigator = navigatorsWidget->primaryUrlNavigator();
0199     m_primaryViewContainer->connectUrlNavigator(primaryNavigator);
0200     if (m_splitViewEnabled) {
0201         auto secondaryNavigator = navigatorsWidget->secondaryUrlNavigator();
0202         m_secondaryViewContainer->connectUrlNavigator(secondaryNavigator);
0203     }
0204     m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer, m_secondaryViewContainer);
0205 }
0206 
0207 void DolphinTabPage::disconnectNavigators()
0208 {
0209     m_navigatorsWidget = nullptr;
0210     m_primaryViewContainer->disconnectUrlNavigator();
0211     if (m_splitViewEnabled) {
0212         m_secondaryViewContainer->disconnectUrlNavigator();
0213     }
0214 }
0215 
0216 void DolphinTabPage::insertNavigatorsWidget(DolphinNavigatorsWidgetAction *navigatorsWidget)
0217 {
0218     QGridLayout *gridLayout = static_cast<QGridLayout *>(layout());
0219     if (navigatorsWidget->isInToolbar()) {
0220         gridLayout->setRowMinimumHeight(0, 0);
0221     } else {
0222         // We set a row minimum height, so the height does not visibly change whenever
0223         // navigatorsWidget is inserted which happens every time the current tab is changed.
0224         gridLayout->setRowMinimumHeight(0, navigatorsWidget->primaryUrlNavigator()->height());
0225         gridLayout->addWidget(navigatorsWidget->requestWidget(this), 0, 0);
0226     }
0227 }
0228 
0229 void DolphinTabPage::markUrlsAsSelected(const QList<QUrl> &urls)
0230 {
0231     m_primaryViewContainer->view()->markUrlsAsSelected(urls);
0232     if (m_splitViewEnabled) {
0233         m_secondaryViewContainer->view()->markUrlsAsSelected(urls);
0234     }
0235 }
0236 
0237 void DolphinTabPage::markUrlAsCurrent(const QUrl &url)
0238 {
0239     m_primaryViewContainer->view()->markUrlAsCurrent(url);
0240     if (m_splitViewEnabled) {
0241         m_secondaryViewContainer->view()->markUrlAsCurrent(url);
0242     }
0243 }
0244 
0245 void DolphinTabPage::refreshViews()
0246 {
0247     m_primaryViewContainer->readSettings();
0248     if (m_splitViewEnabled) {
0249         m_secondaryViewContainer->readSettings();
0250     }
0251 }
0252 
0253 QByteArray DolphinTabPage::saveState() const
0254 {
0255     QByteArray state;
0256     QDataStream stream(&state, QIODevice::WriteOnly);
0257 
0258     stream << quint32(2); // Tab state version
0259 
0260     stream << m_splitViewEnabled;
0261 
0262     stream << m_primaryViewContainer->url();
0263     stream << m_primaryViewContainer->urlNavigatorInternalWithHistory()->isUrlEditable();
0264     m_primaryViewContainer->view()->saveState(stream);
0265 
0266     if (m_splitViewEnabled) {
0267         stream << m_secondaryViewContainer->url();
0268         stream << m_secondaryViewContainer->urlNavigatorInternalWithHistory()->isUrlEditable();
0269         m_secondaryViewContainer->view()->saveState(stream);
0270     }
0271 
0272     stream << m_primaryViewActive;
0273     stream << m_splitter->saveState();
0274 
0275     return state;
0276 }
0277 
0278 void DolphinTabPage::restoreState(const QByteArray &state)
0279 {
0280     if (state.isEmpty()) {
0281         return;
0282     }
0283 
0284     QByteArray sd = state;
0285     QDataStream stream(&sd, QIODevice::ReadOnly);
0286 
0287     // Read the version number of the tab state and check if the version is supported.
0288     quint32 version = 0;
0289     stream >> version;
0290     if (version != 2) {
0291         // The version of the tab state isn't supported, we can't restore it.
0292         return;
0293     }
0294 
0295     bool isSplitViewEnabled = false;
0296     stream >> isSplitViewEnabled;
0297     setSplitViewEnabled(isSplitViewEnabled, WithoutAnimation);
0298 
0299     QUrl primaryUrl;
0300     stream >> primaryUrl;
0301     m_primaryViewContainer->setUrl(primaryUrl);
0302     bool primaryUrlEditable;
0303     stream >> primaryUrlEditable;
0304     m_primaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable);
0305     m_primaryViewContainer->view()->restoreState(stream);
0306 
0307     if (isSplitViewEnabled) {
0308         QUrl secondaryUrl;
0309         stream >> secondaryUrl;
0310         m_secondaryViewContainer->setUrl(secondaryUrl);
0311         bool secondaryUrlEditable;
0312         stream >> secondaryUrlEditable;
0313         m_secondaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable);
0314         m_secondaryViewContainer->view()->restoreState(stream);
0315     }
0316 
0317     stream >> m_primaryViewActive;
0318     if (m_primaryViewActive) {
0319         m_primaryViewContainer->setActive(true);
0320     } else {
0321         Q_ASSERT(m_splitViewEnabled);
0322         m_secondaryViewContainer->setActive(true);
0323     }
0324 
0325     QByteArray splitterState;
0326     stream >> splitterState;
0327     m_splitter->restoreState(splitterState);
0328 }
0329 
0330 void DolphinTabPage::setActive(bool active)
0331 {
0332     if (active) {
0333         m_active = active;
0334     } else {
0335         // we should bypass changing active view in split mode
0336         m_active = !m_splitViewEnabled;
0337     }
0338     // we want view to fire activated when goes from false to true
0339     activeViewContainer()->setActive(active);
0340 }
0341 
0342 void DolphinTabPage::slotAnimationFinished()
0343 {
0344     for (int i = 0; i < m_splitter->count(); ++i) {
0345         QWidget *viewContainer = m_splitter->widget(i);
0346         if (viewContainer != m_primaryViewContainer && viewContainer != m_secondaryViewContainer) {
0347             viewContainer->close();
0348             viewContainer->deleteLater();
0349         }
0350     }
0351     for (int i = 0; i < m_splitter->count(); ++i) {
0352         QWidget *viewContainer = m_splitter->widget(i);
0353         viewContainer->setMinimumWidth(viewContainer->minimumSizeHint().width());
0354     }
0355     m_expandingContainer = nullptr;
0356 }
0357 
0358 void DolphinTabPage::slotAnimationValueChanged(const QVariant &value)
0359 {
0360     Q_CHECK_PTR(m_expandingContainer);
0361     const int indexOfExpandingContainer = m_splitter->indexOf(m_expandingContainer);
0362     int indexOfNonExpandingContainer = -1;
0363     if (m_expandingContainer == m_primaryViewContainer) {
0364         indexOfNonExpandingContainer = m_splitter->indexOf(m_secondaryViewContainer);
0365     } else {
0366         indexOfNonExpandingContainer = m_splitter->indexOf(m_primaryViewContainer);
0367     }
0368     std::vector<QWidget *> widgetsToRemove;
0369     const QList<int> oldSplitterSizes = m_splitter->sizes();
0370     QList<int> newSplitterSizes{oldSplitterSizes};
0371     int expansionWidthNeeded = value.toInt() - oldSplitterSizes.at(indexOfExpandingContainer);
0372 
0373     // Reduce the size of the other widgets to make space for the expandingContainer.
0374     for (int i = m_splitter->count() - 1; i >= 0; --i) {
0375         if (m_splitter->widget(i) == m_primaryViewContainer || m_splitter->widget(i) == m_secondaryViewContainer) {
0376             continue;
0377         }
0378         newSplitterSizes[i] = oldSplitterSizes.at(i) - expansionWidthNeeded;
0379         expansionWidthNeeded = 0;
0380         if (indexOfNonExpandingContainer != -1) {
0381             // Make sure every zombie container is at least slightly reduced in size
0382             // so it doesn't seem like they are here to stay.
0383             newSplitterSizes[i]--;
0384             newSplitterSizes[indexOfNonExpandingContainer]++;
0385         }
0386         if (newSplitterSizes.at(i) <= 0) {
0387             expansionWidthNeeded -= newSplitterSizes.at(i);
0388             newSplitterSizes[i] = 0;
0389             widgetsToRemove.emplace_back(m_splitter->widget(i));
0390         }
0391     }
0392     if (expansionWidthNeeded > 1 && indexOfNonExpandingContainer != -1) {
0393         Q_ASSERT(m_splitViewEnabled);
0394         newSplitterSizes[indexOfNonExpandingContainer] -= expansionWidthNeeded;
0395     }
0396     newSplitterSizes[indexOfExpandingContainer] = value.toInt();
0397     m_splitter->setSizes(newSplitterSizes);
0398     while (!widgetsToRemove.empty()) {
0399         widgetsToRemove.back()->close();
0400         widgetsToRemove.back()->deleteLater();
0401         widgetsToRemove.pop_back();
0402     }
0403 }
0404 
0405 void DolphinTabPage::slotViewActivated()
0406 {
0407     const DolphinView *oldActiveView = activeViewContainer()->view();
0408 
0409     // Set the view, which was active before, to inactive
0410     // and update the active view type, if tab is active
0411     if (m_active) {
0412         if (m_splitViewEnabled) {
0413             activeViewContainer()->setActive(false);
0414             m_primaryViewActive = !m_primaryViewActive;
0415         } else {
0416             m_primaryViewActive = true;
0417             if (m_secondaryViewContainer) {
0418                 m_secondaryViewContainer->setActive(false);
0419             }
0420         }
0421     }
0422 
0423     const DolphinView *newActiveView = activeViewContainer()->view();
0424 
0425     if (newActiveView == oldActiveView) {
0426         return;
0427     }
0428 
0429     disconnect(oldActiveView, &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
0430     disconnect(oldActiveView, &DolphinView::redirection, this, &DolphinTabPage::slotViewUrlRedirection);
0431     connect(newActiveView, &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
0432     connect(newActiveView, &DolphinView::redirection, this, &DolphinTabPage::slotViewUrlRedirection);
0433     Q_EMIT activeViewChanged(activeViewContainer());
0434     Q_EMIT activeViewUrlChanged(activeViewContainer()->url());
0435 }
0436 
0437 void DolphinTabPage::slotViewUrlRedirection(const QUrl &oldUrl, const QUrl &newUrl)
0438 {
0439     Q_UNUSED(oldUrl)
0440 
0441     Q_EMIT activeViewUrlChanged(newUrl);
0442 }
0443 
0444 void DolphinTabPage::switchActiveView()
0445 {
0446     if (!m_splitViewEnabled) {
0447         return;
0448     }
0449     if (m_primaryViewActive) {
0450         m_secondaryViewContainer->setActive(true);
0451     } else {
0452         m_primaryViewContainer->setActive(true);
0453     }
0454 }
0455 
0456 DolphinViewContainer *DolphinTabPage::createViewContainer(const QUrl &url) const
0457 {
0458     DolphinViewContainer *container = new DolphinViewContainer(url, m_splitter);
0459     container->setActive(false);
0460 
0461     const DolphinView *view = container->view();
0462     connect(view, &DolphinView::activated, this, &DolphinTabPage::slotViewActivated);
0463 
0464     connect(view, &DolphinView::toggleActiveViewRequested, this, &DolphinTabPage::switchActiveView);
0465 
0466     return container;
0467 }
0468 
0469 void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer *expandingContainer)
0470 {
0471     Q_CHECK_PTR(expandingContainer);
0472     Q_ASSERT(expandingContainer == m_primaryViewContainer || expandingContainer == m_secondaryViewContainer);
0473     m_expandingContainer = expandingContainer;
0474 
0475     m_expandViewAnimation = new QVariantAnimation(m_splitter);
0476     m_expandViewAnimation->setDuration(2 * style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) * GlobalConfig::animationDurationFactor());
0477     for (int i = 0; i < m_splitter->count(); ++i) {
0478         m_splitter->widget(i)->setMinimumWidth(1);
0479     }
0480     connect(m_expandViewAnimation, &QAbstractAnimation::finished, this, &DolphinTabPage::slotAnimationFinished);
0481     connect(m_expandViewAnimation, &QVariantAnimation::valueChanged, this, &DolphinTabPage::slotAnimationValueChanged);
0482 
0483     m_expandViewAnimation->setStartValue(expandingContainer->width());
0484     if (m_splitViewEnabled) { // A new viewContainer is being opened.
0485         m_expandViewAnimation->setEndValue(m_splitter->width() / 2);
0486         m_expandViewAnimation->setEasingCurve(QEasingCurve::OutCubic);
0487     } else { // A viewContainer is being closed.
0488         m_expandViewAnimation->setEndValue(m_splitter->width());
0489         m_expandViewAnimation->setEasingCurve(QEasingCurve::InCubic);
0490     }
0491     m_expandViewAnimation->start(QAbstractAnimation::DeleteWhenStopped);
0492 }
0493 
0494 DolphinTabPageSplitterHandle::DolphinTabPageSplitterHandle(Qt::Orientation orientation, QSplitter *parent)
0495     : QSplitterHandle(orientation, parent)
0496     , m_mouseReleaseWasReceived(false)
0497 {
0498 }
0499 
0500 bool DolphinTabPageSplitterHandle::event(QEvent *event)
0501 {
0502     switch (event->type()) {
0503     case QEvent::MouseButtonPress:
0504         m_mouseReleaseWasReceived = false;
0505         break;
0506     case QEvent::MouseButtonRelease:
0507         if (m_mouseReleaseWasReceived) {
0508             resetSplitterSizes();
0509         }
0510         m_mouseReleaseWasReceived = !m_mouseReleaseWasReceived;
0511         break;
0512     case QEvent::MouseButtonDblClick:
0513         m_mouseReleaseWasReceived = false;
0514         resetSplitterSizes();
0515         break;
0516     default:
0517         break;
0518     }
0519 
0520     return QSplitterHandle::event(event);
0521 }
0522 
0523 void DolphinTabPageSplitterHandle::resetSplitterSizes()
0524 {
0525     QList<int> splitterSizes = splitter()->sizes();
0526     std::fill(splitterSizes.begin(), splitterSizes.end(), 0);
0527     splitter()->setSizes(splitterSizes);
0528 }
0529 
0530 DolphinTabPageSplitter::DolphinTabPageSplitter(Qt::Orientation orientation, QWidget *parent)
0531     : QSplitter(orientation, parent)
0532 {
0533 }
0534 
0535 QSplitterHandle *DolphinTabPageSplitter::createHandle()
0536 {
0537     return new DolphinTabPageSplitterHandle(orientation(), this);
0538 }
0539 
0540 #include "moc_dolphintabpage.cpp"