File indexing completed on 2024-04-14 15:49:29
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"