2 * SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
3 * SPDX-FileCopyrightText: 2020 Felix Ernst <felixernst@kde.org>
5 * SPDX-License-Identifier: GPL-2.0-or-later
8 #include "dolphintabpage.h"
10 #include "dolphin_generalsettings.h"
11 #include "dolphinviewcontainer.h"
13 #include <QGridLayout>
15 #include <QVariantAnimation>
17 DolphinTabPage::DolphinTabPage(const QUrl
&primaryUrl
, const QUrl
&secondaryUrl
, QWidget
*parent
)
19 , m_expandingContainer
{nullptr}
20 , m_primaryViewActive(true)
21 , m_splitViewEnabled(false)
24 QGridLayout
*layout
= new QGridLayout(this);
25 layout
->setSpacing(0);
26 layout
->setContentsMargins(0, 0, 0, 0);
28 m_splitter
= new DolphinTabPageSplitter(Qt::Horizontal
, this);
29 m_splitter
->setChildrenCollapsible(false);
30 connect(m_splitter
, &QSplitter::splitterMoved
, this, &DolphinTabPage::splitterMoved
);
31 layout
->addWidget(m_splitter
, 1, 0);
32 layout
->setRowStretch(1, 1);
34 // Create a new primary view
35 m_primaryViewContainer
= createViewContainer(primaryUrl
);
36 connect(m_primaryViewContainer
->view(), &DolphinView::urlChanged
, this, &DolphinTabPage::activeViewUrlChanged
);
37 connect(m_primaryViewContainer
->view(), &DolphinView::redirection
, this, &DolphinTabPage::slotViewUrlRedirection
);
39 m_splitter
->addWidget(m_primaryViewContainer
);
40 m_primaryViewContainer
->show();
42 if (secondaryUrl
.isValid() || GeneralSettings::splitView()) {
43 // Provide a secondary view, if the given secondary url is valid or if the
44 // startup settings are set this way (use the url of the primary view).
45 m_splitViewEnabled
= true;
46 const QUrl
&url
= secondaryUrl
.isValid() ? secondaryUrl
: primaryUrl
;
47 m_secondaryViewContainer
= createViewContainer(url
);
48 connect(m_secondaryViewContainer
->view(), &DolphinView::redirection
, this, &DolphinTabPage::slotViewUrlRedirection
);
49 m_splitter
->addWidget(m_secondaryViewContainer
);
50 m_secondaryViewContainer
->show();
53 m_primaryViewContainer
->setActive(true);
56 bool DolphinTabPage::primaryViewActive() const
58 return m_primaryViewActive
;
61 bool DolphinTabPage::splitViewEnabled() const
63 return m_splitViewEnabled
;
66 void DolphinTabPage::setSplitViewEnabled(bool enabled
, Animated animated
, const QUrl
&secondaryUrl
)
68 if (m_splitViewEnabled
!= enabled
) {
69 m_splitViewEnabled
= enabled
;
70 if (animated
== WithAnimation
71 && (style()->styleHint(QStyle::SH_Widget_Animation_Duration
, nullptr, this) < 1 || GlobalConfig::animationDurationFactor() <= 0.0)) {
72 animated
= WithoutAnimation
;
74 if (m_expandViewAnimation
) {
75 m_expandViewAnimation
->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped.
76 if (animated
== WithoutAnimation
) {
77 slotAnimationFinished();
82 QList
<int> splitterSizes
= m_splitter
->sizes();
83 const QUrl
&url
= (secondaryUrl
.isEmpty()) ? m_primaryViewContainer
->url() : secondaryUrl
;
84 m_secondaryViewContainer
= createViewContainer(url
);
86 auto secondaryNavigator
= m_navigatorsWidget
->secondaryUrlNavigator();
87 if (!secondaryNavigator
) {
88 m_navigatorsWidget
->createSecondaryUrlNavigator();
89 secondaryNavigator
= m_navigatorsWidget
->secondaryUrlNavigator();
91 m_secondaryViewContainer
->connectUrlNavigator(secondaryNavigator
);
92 connect(m_secondaryViewContainer
->view(), &DolphinView::redirection
, this, &DolphinTabPage::slotViewUrlRedirection
);
93 m_navigatorsWidget
->setSecondaryNavigatorVisible(true);
94 m_navigatorsWidget
->followViewContainersGeometry(m_primaryViewContainer
, m_secondaryViewContainer
);
96 m_splitter
->addWidget(m_secondaryViewContainer
);
97 m_secondaryViewContainer
->setActive(true);
99 if (animated
== WithAnimation
) {
100 m_secondaryViewContainer
->setMinimumWidth(1);
101 splitterSizes
.append(1);
102 m_splitter
->setSizes(splitterSizes
);
103 startExpandViewAnimation(m_secondaryViewContainer
);
105 m_secondaryViewContainer
->show();
107 m_navigatorsWidget
->setSecondaryNavigatorVisible(false);
108 m_secondaryViewContainer
->disconnectUrlNavigator();
109 disconnect(m_secondaryViewContainer
->view(), &DolphinView::redirection
, this, &DolphinTabPage::slotViewUrlRedirection
);
111 DolphinViewContainer
*view
;
112 if (GeneralSettings::closeActiveSplitView()) {
113 view
= activeViewContainer();
114 if (m_primaryViewActive
) {
115 m_primaryViewContainer
->disconnectUrlNavigator();
116 m_secondaryViewContainer
->connectUrlNavigator(m_navigatorsWidget
->primaryUrlNavigator());
118 // If the primary view is active, we have to swap the pointers
119 // because the secondary view will be the new primary view.
120 std::swap(m_primaryViewContainer
, m_secondaryViewContainer
);
121 m_primaryViewActive
= false;
124 view
= m_primaryViewActive
? m_secondaryViewContainer
: m_primaryViewContainer
;
125 if (!m_primaryViewActive
) {
126 m_primaryViewContainer
->disconnectUrlNavigator();
127 m_secondaryViewContainer
->connectUrlNavigator(m_navigatorsWidget
->primaryUrlNavigator());
129 // If the secondary view is active, we have to swap the pointers
130 // because the secondary view will be the new primary view.
131 std::swap(m_primaryViewContainer
, m_secondaryViewContainer
);
132 m_primaryViewActive
= true;
135 m_primaryViewContainer
->setActive(true);
136 m_navigatorsWidget
->followViewContainersGeometry(m_primaryViewContainer
, nullptr);
138 if (animated
== WithoutAnimation
) {
142 // Kill it but keep it as a zombie for the closing animation.
143 m_secondaryViewContainer
= nullptr;
144 view
->blockSignals(true);
145 view
->view()->blockSignals(true);
146 view
->setDisabled(true);
147 startExpandViewAnimation(m_primaryViewContainer
);
150 m_primaryViewContainer
->slotSplitTabDisabled();
155 DolphinViewContainer
*DolphinTabPage::primaryViewContainer() const
157 return m_primaryViewContainer
;
160 DolphinViewContainer
*DolphinTabPage::secondaryViewContainer() const
162 return m_secondaryViewContainer
;
165 DolphinViewContainer
*DolphinTabPage::activeViewContainer() const
167 return m_primaryViewActive
? m_primaryViewContainer
: m_secondaryViewContainer
;
170 DolphinViewContainer
*DolphinTabPage::inactiveViewContainer() const
172 if (!splitViewEnabled()) {
176 return primaryViewActive() ? secondaryViewContainer() : primaryViewContainer();
179 KFileItemList
DolphinTabPage::selectedItems() const
181 KFileItemList items
= m_primaryViewContainer
->view()->selectedItems();
182 if (m_splitViewEnabled
) {
183 items
+= m_secondaryViewContainer
->view()->selectedItems();
188 int DolphinTabPage::selectedItemsCount() const
190 int selectedItemsCount
= m_primaryViewContainer
->view()->selectedItemsCount();
191 if (m_splitViewEnabled
) {
192 selectedItemsCount
+= m_secondaryViewContainer
->view()->selectedItemsCount();
194 return selectedItemsCount
;
197 void DolphinTabPage::connectNavigators(DolphinNavigatorsWidgetAction
*navigatorsWidget
)
199 insertNavigatorsWidget(navigatorsWidget
);
200 m_navigatorsWidget
= navigatorsWidget
;
201 auto primaryNavigator
= navigatorsWidget
->primaryUrlNavigator();
202 m_primaryViewContainer
->connectUrlNavigator(primaryNavigator
);
203 if (m_splitViewEnabled
) {
204 auto secondaryNavigator
= navigatorsWidget
->secondaryUrlNavigator();
205 m_secondaryViewContainer
->connectUrlNavigator(secondaryNavigator
);
207 m_navigatorsWidget
->followViewContainersGeometry(m_primaryViewContainer
, m_secondaryViewContainer
);
210 void DolphinTabPage::disconnectNavigators()
212 m_navigatorsWidget
= nullptr;
213 m_primaryViewContainer
->disconnectUrlNavigator();
214 if (m_splitViewEnabled
) {
215 m_secondaryViewContainer
->disconnectUrlNavigator();
219 void DolphinTabPage::insertNavigatorsWidget(DolphinNavigatorsWidgetAction
*navigatorsWidget
)
221 QGridLayout
*gridLayout
= static_cast<QGridLayout
*>(layout());
222 if (navigatorsWidget
->isInToolbar()) {
223 gridLayout
->setRowMinimumHeight(0, 0);
225 // We set a row minimum height, so the height does not visibly change whenever
226 // navigatorsWidget is inserted which happens every time the current tab is changed.
227 gridLayout
->setRowMinimumHeight(0, navigatorsWidget
->primaryUrlNavigator()->height());
228 gridLayout
->addWidget(navigatorsWidget
->requestWidget(this), 0, 0);
232 void DolphinTabPage::markUrlsAsSelected(const QList
<QUrl
> &urls
)
234 m_primaryViewContainer
->view()->markUrlsAsSelected(urls
);
235 if (m_splitViewEnabled
) {
236 m_secondaryViewContainer
->view()->markUrlsAsSelected(urls
);
240 void DolphinTabPage::markUrlAsCurrent(const QUrl
&url
)
242 m_primaryViewContainer
->view()->markUrlAsCurrent(url
);
243 if (m_splitViewEnabled
) {
244 m_secondaryViewContainer
->view()->markUrlAsCurrent(url
);
248 void DolphinTabPage::refreshViews()
250 m_primaryViewContainer
->readSettings();
251 if (m_splitViewEnabled
) {
252 m_secondaryViewContainer
->readSettings();
256 QByteArray
DolphinTabPage::saveState() const
259 QDataStream
stream(&state
, QIODevice::WriteOnly
);
261 stream
<< quint32(2); // Tab state version
263 stream
<< m_splitViewEnabled
;
265 stream
<< m_primaryViewContainer
->url();
266 stream
<< m_primaryViewContainer
->urlNavigatorInternalWithHistory()->isUrlEditable();
267 m_primaryViewContainer
->view()->saveState(stream
);
269 if (m_splitViewEnabled
) {
270 stream
<< m_secondaryViewContainer
->url();
271 stream
<< m_secondaryViewContainer
->urlNavigatorInternalWithHistory()->isUrlEditable();
272 m_secondaryViewContainer
->view()->saveState(stream
);
275 stream
<< m_primaryViewActive
;
276 stream
<< m_splitter
->saveState();
278 if (!m_customLabel
.isEmpty()) {
279 stream
<< m_customLabel
;
285 void DolphinTabPage::restoreState(const QByteArray
&state
)
287 if (state
.isEmpty()) {
291 QByteArray sd
= state
;
292 QDataStream
stream(&sd
, QIODevice::ReadOnly
);
294 // Read the version number of the tab state and check if the version is supported.
298 // The version of the tab state isn't supported, we can't restore it.
302 bool isSplitViewEnabled
= false;
303 stream
>> isSplitViewEnabled
;
304 setSplitViewEnabled(isSplitViewEnabled
, WithoutAnimation
);
307 stream
>> primaryUrl
;
308 m_primaryViewContainer
->setUrl(primaryUrl
);
309 bool primaryUrlEditable
;
310 stream
>> primaryUrlEditable
;
311 m_primaryViewContainer
->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable
);
312 m_primaryViewContainer
->view()->restoreState(stream
);
314 if (isSplitViewEnabled
) {
316 stream
>> secondaryUrl
;
317 m_secondaryViewContainer
->setUrl(secondaryUrl
);
318 bool secondaryUrlEditable
;
319 stream
>> secondaryUrlEditable
;
320 m_secondaryViewContainer
->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable
);
321 m_secondaryViewContainer
->view()->restoreState(stream
);
324 stream
>> m_primaryViewActive
;
325 if (m_primaryViewActive
) {
326 m_primaryViewContainer
->setActive(true);
328 Q_ASSERT(m_splitViewEnabled
);
329 m_secondaryViewContainer
->setActive(true);
332 QByteArray splitterState
;
333 stream
>> splitterState
;
334 m_splitter
->restoreState(splitterState
);
336 if (!stream
.atEnd()) {
339 setCustomLabel(tabTitle
);
343 void DolphinTabPage::setActive(bool active
)
348 // we should bypass changing active view in split mode
349 m_active
= !m_splitViewEnabled
;
351 // we want view to fire activated when goes from false to true
352 activeViewContainer()->setActive(active
);
355 void DolphinTabPage::setCustomLabel(const QString
&label
)
357 m_customLabel
= label
;
360 QString
DolphinTabPage::customLabel() const
362 return m_customLabel
;
365 void DolphinTabPage::slotAnimationFinished()
367 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
368 QWidget
*viewContainer
= m_splitter
->widget(i
);
369 if (viewContainer
!= m_primaryViewContainer
&& viewContainer
!= m_secondaryViewContainer
) {
370 viewContainer
->close();
371 viewContainer
->deleteLater();
374 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
375 QWidget
*viewContainer
= m_splitter
->widget(i
);
376 viewContainer
->setMinimumWidth(viewContainer
->minimumSizeHint().width());
378 m_expandingContainer
= nullptr;
381 void DolphinTabPage::slotAnimationValueChanged(const QVariant
&value
)
383 Q_CHECK_PTR(m_expandingContainer
);
384 const int indexOfExpandingContainer
= m_splitter
->indexOf(m_expandingContainer
);
385 int indexOfNonExpandingContainer
= -1;
386 if (m_expandingContainer
== m_primaryViewContainer
) {
387 indexOfNonExpandingContainer
= m_splitter
->indexOf(m_secondaryViewContainer
);
389 indexOfNonExpandingContainer
= m_splitter
->indexOf(m_primaryViewContainer
);
391 std::vector
<QWidget
*> widgetsToRemove
;
392 const QList
<int> oldSplitterSizes
= m_splitter
->sizes();
393 QList
<int> newSplitterSizes
{oldSplitterSizes
};
394 int expansionWidthNeeded
= value
.toInt() - oldSplitterSizes
.at(indexOfExpandingContainer
);
396 // Reduce the size of the other widgets to make space for the expandingContainer.
397 for (int i
= m_splitter
->count() - 1; i
>= 0; --i
) {
398 if (m_splitter
->widget(i
) == m_primaryViewContainer
|| m_splitter
->widget(i
) == m_secondaryViewContainer
) {
401 newSplitterSizes
[i
] = oldSplitterSizes
.at(i
) - expansionWidthNeeded
;
402 expansionWidthNeeded
= 0;
403 if (indexOfNonExpandingContainer
!= -1) {
404 // Make sure every zombie container is at least slightly reduced in size
405 // so it doesn't seem like they are here to stay.
406 newSplitterSizes
[i
]--;
407 newSplitterSizes
[indexOfNonExpandingContainer
]++;
409 if (newSplitterSizes
.at(i
) <= 0) {
410 expansionWidthNeeded
-= newSplitterSizes
.at(i
);
411 newSplitterSizes
[i
] = 0;
412 widgetsToRemove
.emplace_back(m_splitter
->widget(i
));
415 if (expansionWidthNeeded
> 1 && indexOfNonExpandingContainer
!= -1) {
416 Q_ASSERT(m_splitViewEnabled
);
417 newSplitterSizes
[indexOfNonExpandingContainer
] -= expansionWidthNeeded
;
419 newSplitterSizes
[indexOfExpandingContainer
] = value
.toInt();
420 m_splitter
->setSizes(newSplitterSizes
);
421 while (!widgetsToRemove
.empty()) {
422 widgetsToRemove
.back()->close();
423 widgetsToRemove
.back()->deleteLater();
424 widgetsToRemove
.pop_back();
428 void DolphinTabPage::slotViewActivated()
430 const DolphinView
*oldActiveView
= activeViewContainer()->view();
432 // Set the view, which was active before, to inactive
433 // and update the active view type, if tab is active
435 if (m_splitViewEnabled
) {
436 activeViewContainer()->setActive(false);
437 m_primaryViewActive
= !m_primaryViewActive
;
439 m_primaryViewActive
= true;
440 if (m_secondaryViewContainer
) {
441 m_secondaryViewContainer
->setActive(false);
446 const DolphinView
*newActiveView
= activeViewContainer()->view();
448 if (newActiveView
== oldActiveView
) {
452 disconnect(oldActiveView
, &DolphinView::urlChanged
, this, &DolphinTabPage::activeViewUrlChanged
);
453 connect(newActiveView
, &DolphinView::urlChanged
, this, &DolphinTabPage::activeViewUrlChanged
);
454 Q_EMIT
activeViewChanged(activeViewContainer());
455 Q_EMIT
activeViewUrlChanged(activeViewContainer()->url());
458 void DolphinTabPage::slotViewUrlRedirection(const QUrl
&oldUrl
, const QUrl
&newUrl
)
460 // Make sure the url of the view is updated. BUG:496414
461 if (splitViewEnabled()) {
462 if (primaryViewContainer()->view()->url() == oldUrl
) {
463 primaryViewContainer()->view()->setUrl(newUrl
);
465 if (secondaryViewContainer()->view()->url() == oldUrl
) {
466 secondaryViewContainer()->view()->setUrl(newUrl
);
469 activeViewContainer()->view()->setUrl(newUrl
);
471 Q_EMIT
activeViewUrlChanged(newUrl
);
474 void DolphinTabPage::switchActiveView()
476 if (!m_splitViewEnabled
) {
479 if (m_primaryViewActive
) {
480 m_secondaryViewContainer
->setActive(true);
482 m_primaryViewContainer
->setActive(true);
486 DolphinViewContainer
*DolphinTabPage::createViewContainer(const QUrl
&url
) const
488 DolphinViewContainer
*container
= new DolphinViewContainer(url
, m_splitter
);
489 container
->setActive(false);
491 const DolphinView
*view
= container
->view();
492 connect(view
, &DolphinView::activated
, this, &DolphinTabPage::slotViewActivated
);
494 connect(view
, &DolphinView::toggleActiveViewRequested
, this, &DolphinTabPage::switchActiveView
);
499 void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer
*expandingContainer
)
501 Q_CHECK_PTR(expandingContainer
);
502 Q_ASSERT(expandingContainer
== m_primaryViewContainer
|| expandingContainer
== m_secondaryViewContainer
);
503 m_expandingContainer
= expandingContainer
;
505 m_expandViewAnimation
= new QVariantAnimation(m_splitter
);
506 m_expandViewAnimation
->setDuration(2 * style()->styleHint(QStyle::SH_Widget_Animation_Duration
, nullptr, this) * GlobalConfig::animationDurationFactor());
507 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
508 m_splitter
->widget(i
)->setMinimumWidth(1);
510 connect(m_expandViewAnimation
, &QAbstractAnimation::finished
, this, &DolphinTabPage::slotAnimationFinished
);
511 connect(m_expandViewAnimation
, &QVariantAnimation::valueChanged
, this, &DolphinTabPage::slotAnimationValueChanged
);
513 m_expandViewAnimation
->setStartValue(expandingContainer
->width());
514 if (m_splitViewEnabled
) { // A new viewContainer is being opened.
515 m_expandViewAnimation
->setEndValue(m_splitter
->width() / 2);
516 m_expandViewAnimation
->setEasingCurve(QEasingCurve::OutCubic
);
517 } else { // A viewContainer is being closed.
518 m_expandViewAnimation
->setEndValue(m_splitter
->width());
519 m_expandViewAnimation
->setEasingCurve(QEasingCurve::InCubic
);
521 m_expandViewAnimation
->start(QAbstractAnimation::DeleteWhenStopped
);
524 DolphinTabPageSplitterHandle::DolphinTabPageSplitterHandle(Qt::Orientation orientation
, QSplitter
*parent
)
525 : QSplitterHandle(orientation
, parent
)
526 , m_mouseReleaseWasReceived(false)
530 bool DolphinTabPageSplitterHandle::event(QEvent
*event
)
532 switch (event
->type()) {
533 case QEvent::MouseButtonPress
:
534 m_mouseReleaseWasReceived
= false;
536 case QEvent::MouseButtonRelease
:
537 if (m_mouseReleaseWasReceived
) {
538 resetSplitterSizes();
540 m_mouseReleaseWasReceived
= !m_mouseReleaseWasReceived
;
542 case QEvent::MouseButtonDblClick
:
543 m_mouseReleaseWasReceived
= false;
544 resetSplitterSizes();
550 return QSplitterHandle::event(event
);
553 void DolphinTabPageSplitterHandle::resetSplitterSizes()
555 QList
<int> splitterSizes
= splitter()->sizes();
556 std::fill(splitterSizes
.begin(), splitterSizes
.end(), 0);
557 splitter()->setSizes(splitterSizes
);
560 DolphinTabPageSplitter::DolphinTabPageSplitter(Qt::Orientation orientation
, QWidget
*parent
)
561 : QSplitter(orientation
, parent
)
565 QSplitterHandle
*DolphinTabPageSplitter::createHandle()
567 return new DolphinTabPageSplitterHandle(orientation(), this);
570 #include "moc_dolphintabpage.cpp"