2 * SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
3 * SPDX-FileCopyrightText: 2020 Felix Ernst <fe.a.ernst@gmail.com>
5 * SPDX-License-Identifier: GPL-2.0-or-later
8 #include "dolphintabpage.h"
10 #include "dolphin_generalsettings.h"
11 #include "dolphinviewcontainer.h"
14 #include <QVariantAnimation>
16 #include <QGridLayout>
17 #include <QWidgetAction>
20 DolphinTabPage::DolphinTabPage(const QUrl
&primaryUrl
, const QUrl
&secondaryUrl
, QWidget
* parent
) :
22 m_expandingContainer
{nullptr},
23 m_primaryViewActive(true),
24 m_splitViewEnabled(false),
27 QGridLayout
*layout
= new QGridLayout(this);
28 layout
->setSpacing(0);
29 layout
->setContentsMargins(0, 0, 0, 0);
31 m_splitter
= new QSplitter(Qt::Horizontal
, this);
32 m_splitter
->setChildrenCollapsible(false);
33 connect(m_splitter
, &QSplitter::splitterMoved
,
34 this, &DolphinTabPage::splitterMoved
);
35 layout
->addWidget(m_splitter
, 1, 0);
36 layout
->setRowStretch(1, 1);
38 // Create a new primary view
39 m_primaryViewContainer
= createViewContainer(primaryUrl
);
40 connect(m_primaryViewContainer
->view(), &DolphinView::urlChanged
,
41 this, &DolphinTabPage::activeViewUrlChanged
);
42 connect(m_primaryViewContainer
->view(), &DolphinView::redirection
,
43 this, &DolphinTabPage::slotViewUrlRedirection
);
45 m_splitter
->addWidget(m_primaryViewContainer
);
46 m_primaryViewContainer
->installEventFilter(this);
47 m_primaryViewContainer
->show();
49 if (secondaryUrl
.isValid() || GeneralSettings::splitView()) {
50 // Provide a secondary view, if the given secondary url is valid or if the
51 // startup settings are set this way (use the url of the primary view).
52 m_splitViewEnabled
= true;
53 const QUrl
& url
= secondaryUrl
.isValid() ? secondaryUrl
: primaryUrl
;
54 m_secondaryViewContainer
= createViewContainer(url
);
55 m_splitter
->addWidget(m_secondaryViewContainer
);
56 m_secondaryViewContainer
->installEventFilter(this);
57 m_secondaryViewContainer
->show();
60 m_primaryViewContainer
->setActive(true);
63 bool DolphinTabPage::primaryViewActive() const
65 return m_primaryViewActive
;
68 bool DolphinTabPage::splitViewEnabled() const
70 return m_splitViewEnabled
;
73 void DolphinTabPage::setSplitViewEnabled(bool enabled
, Animated animated
, const QUrl
&secondaryUrl
)
75 if (m_splitViewEnabled
!= enabled
) {
76 m_splitViewEnabled
= enabled
;
77 if (animated
== WithAnimation
&& (
78 style()->styleHint(QStyle::SH_Widget_Animation_Duration
, nullptr, this) < 1 ||
79 GlobalConfig::animationDurationFactor() <= 0.0)) {
80 animated
= WithoutAnimation
;
82 if (m_expandViewAnimation
) {
83 m_expandViewAnimation
->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped.
84 if (animated
== WithoutAnimation
) {
85 slotAnimationFinished();
90 QList
<int> splitterSizes
= m_splitter
->sizes();
91 const QUrl
& url
= (secondaryUrl
.isEmpty()) ? m_primaryViewContainer
->url() : secondaryUrl
;
92 m_secondaryViewContainer
= createViewContainer(url
);
94 auto secondaryNavigator
= m_navigatorsWidget
->secondaryUrlNavigator();
95 if (!secondaryNavigator
) {
96 m_navigatorsWidget
->createSecondaryUrlNavigator();
97 secondaryNavigator
= m_navigatorsWidget
->secondaryUrlNavigator();
99 m_secondaryViewContainer
->connectUrlNavigator(secondaryNavigator
);
100 m_navigatorsWidget
->setSecondaryNavigatorVisible(true);
102 m_splitter
->addWidget(m_secondaryViewContainer
);
103 m_secondaryViewContainer
->installEventFilter(this);
104 m_secondaryViewContainer
->setActive(true);
106 if (animated
== WithAnimation
) {
107 m_secondaryViewContainer
->setMinimumWidth(1);
108 splitterSizes
.append(1);
109 m_splitter
->setSizes(splitterSizes
);
110 startExpandViewAnimation(m_secondaryViewContainer
);
112 m_secondaryViewContainer
->show();
114 m_navigatorsWidget
->setSecondaryNavigatorVisible(false);
115 m_secondaryViewContainer
->disconnectUrlNavigator();
117 DolphinViewContainer
* view
;
118 if (GeneralSettings::closeActiveSplitView()) {
119 view
= activeViewContainer();
120 if (m_primaryViewActive
) {
121 m_primaryViewContainer
->disconnectUrlNavigator();
122 m_secondaryViewContainer
->connectUrlNavigator(
123 m_navigatorsWidget
->primaryUrlNavigator());
125 // If the primary view is active, we have to swap the pointers
126 // because the secondary view will be the new primary view.
127 qSwap(m_primaryViewContainer
, m_secondaryViewContainer
);
128 m_primaryViewActive
= false;
131 view
= m_primaryViewActive
? m_secondaryViewContainer
: m_primaryViewContainer
;
132 if (!m_primaryViewActive
) {
133 m_primaryViewContainer
->disconnectUrlNavigator();
134 m_secondaryViewContainer
->connectUrlNavigator(
135 m_navigatorsWidget
->primaryUrlNavigator());
137 // If the secondary view is active, we have to swap the pointers
138 // because the secondary view will be the new primary view.
139 qSwap(m_primaryViewContainer
, m_secondaryViewContainer
);
140 m_primaryViewActive
= true;
143 m_primaryViewContainer
->setActive(true);
145 if (animated
== WithoutAnimation
) {
149 // Kill it but keep it as a zombie for the closing animation.
150 m_secondaryViewContainer
= nullptr;
151 view
->blockSignals(true);
152 view
->view()->blockSignals(true);
153 view
->setDisabled(true);
154 startExpandViewAnimation(m_primaryViewContainer
);
160 DolphinViewContainer
* DolphinTabPage::primaryViewContainer() const
162 return m_primaryViewContainer
;
165 DolphinViewContainer
* DolphinTabPage::secondaryViewContainer() const
167 return m_secondaryViewContainer
;
170 DolphinViewContainer
* DolphinTabPage::activeViewContainer() const
172 return m_primaryViewActive
? m_primaryViewContainer
:
173 m_secondaryViewContainer
;
176 KFileItemList
DolphinTabPage::selectedItems() const
178 KFileItemList items
= m_primaryViewContainer
->view()->selectedItems();
179 if (m_splitViewEnabled
) {
180 items
+= m_secondaryViewContainer
->view()->selectedItems();
185 int DolphinTabPage::selectedItemsCount() const
187 int selectedItemsCount
= m_primaryViewContainer
->view()->selectedItemsCount();
188 if (m_splitViewEnabled
) {
189 selectedItemsCount
+= m_secondaryViewContainer
->view()->selectedItemsCount();
191 return selectedItemsCount
;
194 void DolphinTabPage::connectNavigators(DolphinNavigatorsWidgetAction
*navigatorsWidget
)
196 insertNavigatorsWidget(navigatorsWidget
);
197 m_navigatorsWidget
= navigatorsWidget
;
198 auto primaryNavigator
= navigatorsWidget
->primaryUrlNavigator();
199 m_primaryViewContainer
->connectUrlNavigator(primaryNavigator
);
200 if (m_splitViewEnabled
) {
201 auto secondaryNavigator
= navigatorsWidget
->secondaryUrlNavigator();
202 m_secondaryViewContainer
->connectUrlNavigator(secondaryNavigator
);
207 void DolphinTabPage::disconnectNavigators()
209 m_navigatorsWidget
= nullptr;
210 m_primaryViewContainer
->disconnectUrlNavigator();
211 if (m_splitViewEnabled
) {
212 m_secondaryViewContainer
->disconnectUrlNavigator();
216 bool DolphinTabPage::eventFilter(QObject
*watched
, QEvent
*event
)
218 if (event
->type() == QEvent::Resize
&& m_navigatorsWidget
) {
222 return QWidget::eventFilter(watched
, event
);
225 void DolphinTabPage::insertNavigatorsWidget(DolphinNavigatorsWidgetAction
* navigatorsWidget
)
227 QGridLayout
*gridLayout
= static_cast<QGridLayout
*>(layout());
228 if (navigatorsWidget
->isInToolbar()) {
229 gridLayout
->setRowMinimumHeight(0, 0);
231 // We set a row minimum height, so the height does not visibly change whenever
232 // navigatorsWidget is inserted which happens every time the current tab is changed.
233 gridLayout
->setRowMinimumHeight(0, navigatorsWidget
->primaryUrlNavigator()->height());
234 gridLayout
->addWidget(navigatorsWidget
->requestWidget(this), 0, 0);
239 void DolphinTabPage::resizeNavigators() const
241 if (!m_secondaryViewContainer
) {
242 m_navigatorsWidget
->followViewContainerGeometry(
243 m_primaryViewContainer
->mapToGlobal(QPoint(0,0)).x(),
244 m_primaryViewContainer
->width());
246 m_navigatorsWidget
->followViewContainersGeometry(
247 m_primaryViewContainer
->mapToGlobal(QPoint(0,0)).x(),
248 m_primaryViewContainer
->width(),
249 m_secondaryViewContainer
->mapToGlobal(QPoint(0,0)).x(),
250 m_secondaryViewContainer
->width());
254 void DolphinTabPage::markUrlsAsSelected(const QList
<QUrl
>& urls
)
256 m_primaryViewContainer
->view()->markUrlsAsSelected(urls
);
257 if (m_splitViewEnabled
) {
258 m_secondaryViewContainer
->view()->markUrlsAsSelected(urls
);
262 void DolphinTabPage::markUrlAsCurrent(const QUrl
& url
)
264 m_primaryViewContainer
->view()->markUrlAsCurrent(url
);
265 if (m_splitViewEnabled
) {
266 m_secondaryViewContainer
->view()->markUrlAsCurrent(url
);
270 void DolphinTabPage::refreshViews()
272 m_primaryViewContainer
->readSettings();
273 if (m_splitViewEnabled
) {
274 m_secondaryViewContainer
->readSettings();
278 QByteArray
DolphinTabPage::saveState() const
281 QDataStream
stream(&state
, QIODevice::WriteOnly
);
283 stream
<< quint32(2); // Tab state version
285 stream
<< m_splitViewEnabled
;
287 stream
<< m_primaryViewContainer
->url();
288 stream
<< m_primaryViewContainer
->urlNavigatorInternalWithHistory()->isUrlEditable();
289 m_primaryViewContainer
->view()->saveState(stream
);
291 if (m_splitViewEnabled
) {
292 stream
<< m_secondaryViewContainer
->url();
293 stream
<< m_secondaryViewContainer
->urlNavigatorInternalWithHistory()->isUrlEditable();
294 m_secondaryViewContainer
->view()->saveState(stream
);
297 stream
<< m_primaryViewActive
;
298 stream
<< m_splitter
->saveState();
303 void DolphinTabPage::restoreState(const QByteArray
& state
)
305 if (state
.isEmpty()) {
309 QByteArray sd
= state
;
310 QDataStream
stream(&sd
, QIODevice::ReadOnly
);
312 // Read the version number of the tab state and check if the version is supported.
316 // The version of the tab state isn't supported, we can't restore it.
320 bool isSplitViewEnabled
= false;
321 stream
>> isSplitViewEnabled
;
322 setSplitViewEnabled(isSplitViewEnabled
, WithoutAnimation
);
325 stream
>> primaryUrl
;
326 m_primaryViewContainer
->setUrl(primaryUrl
);
327 bool primaryUrlEditable
;
328 stream
>> primaryUrlEditable
;
329 m_primaryViewContainer
->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable
);
330 m_primaryViewContainer
->view()->restoreState(stream
);
332 if (isSplitViewEnabled
) {
334 stream
>> secondaryUrl
;
335 m_secondaryViewContainer
->setUrl(secondaryUrl
);
336 bool secondaryUrlEditable
;
337 stream
>> secondaryUrlEditable
;
338 m_secondaryViewContainer
->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable
);
339 m_secondaryViewContainer
->view()->restoreState(stream
);
342 stream
>> m_primaryViewActive
;
343 if (m_primaryViewActive
) {
344 m_primaryViewContainer
->setActive(true);
346 Q_ASSERT(m_splitViewEnabled
);
347 m_secondaryViewContainer
->setActive(true);
350 QByteArray splitterState
;
351 stream
>> splitterState
;
352 m_splitter
->restoreState(splitterState
);
355 void DolphinTabPage::restoreStateV1(const QByteArray
& state
)
357 if (state
.isEmpty()) {
361 QByteArray sd
= state
;
362 QDataStream
stream(&sd
, QIODevice::ReadOnly
);
364 bool isSplitViewEnabled
= false;
365 stream
>> isSplitViewEnabled
;
366 setSplitViewEnabled(isSplitViewEnabled
, WithoutAnimation
);
369 stream
>> primaryUrl
;
370 m_primaryViewContainer
->setUrl(primaryUrl
);
371 bool primaryUrlEditable
;
372 stream
>> primaryUrlEditable
;
373 m_primaryViewContainer
->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable
);
375 if (isSplitViewEnabled
) {
377 stream
>> secondaryUrl
;
378 m_secondaryViewContainer
->setUrl(secondaryUrl
);
379 bool secondaryUrlEditable
;
380 stream
>> secondaryUrlEditable
;
381 m_secondaryViewContainer
->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable
);
384 stream
>> m_primaryViewActive
;
385 if (m_primaryViewActive
) {
386 m_primaryViewContainer
->setActive(true);
388 Q_ASSERT(m_splitViewEnabled
);
389 m_secondaryViewContainer
->setActive(true);
392 QByteArray splitterState
;
393 stream
>> splitterState
;
394 m_splitter
->restoreState(splitterState
);
397 void DolphinTabPage::setActive(bool active
)
402 // we should bypass changing active view in split mode
403 m_active
= !m_splitViewEnabled
;
405 // we want view to fire activated when goes from false to true
406 activeViewContainer()->setActive(active
);
409 void DolphinTabPage::slotAnimationFinished()
411 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
412 QWidget
*viewContainer
= m_splitter
->widget(i
);
413 if (viewContainer
!= m_primaryViewContainer
&&
414 viewContainer
!= m_secondaryViewContainer
) {
415 viewContainer
->close();
416 viewContainer
->deleteLater();
419 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
420 QWidget
*viewContainer
= m_splitter
->widget(i
);
421 viewContainer
->setMinimumWidth(viewContainer
->minimumSizeHint().width());
423 m_expandingContainer
= nullptr;
426 void DolphinTabPage::slotAnimationValueChanged(const QVariant
& value
)
428 Q_CHECK_PTR(m_expandingContainer
);
429 const int indexOfExpandingContainer
= m_splitter
->indexOf(m_expandingContainer
);
430 int indexOfNonExpandingContainer
= -1;
431 if (m_expandingContainer
== m_primaryViewContainer
) {
432 indexOfNonExpandingContainer
= m_splitter
->indexOf(m_secondaryViewContainer
);
434 indexOfNonExpandingContainer
= m_splitter
->indexOf(m_primaryViewContainer
);
436 std::vector
<QWidget
*> widgetsToRemove
;
437 const QList
<int> oldSplitterSizes
= m_splitter
->sizes();
438 QList
<int> newSplitterSizes
{oldSplitterSizes
};
439 int expansionWidthNeeded
= value
.toInt() - oldSplitterSizes
.at(indexOfExpandingContainer
);
441 // Reduce the size of the other widgets to make space for the expandingContainer.
442 for (int i
= m_splitter
->count() - 1; i
>= 0; --i
) {
443 if (m_splitter
->widget(i
) == m_primaryViewContainer
||
444 m_splitter
->widget(i
) == m_secondaryViewContainer
) {
447 newSplitterSizes
[i
] = oldSplitterSizes
.at(i
) - expansionWidthNeeded
;
448 expansionWidthNeeded
= 0;
449 if (indexOfNonExpandingContainer
!= -1) {
450 // Make sure every zombie container is at least slightly reduced in size
451 // so it doesn't seem like they are here to stay.
452 newSplitterSizes
[i
]--;
453 newSplitterSizes
[indexOfNonExpandingContainer
]++;
455 if (newSplitterSizes
.at(i
) <= 0) {
456 expansionWidthNeeded
-= newSplitterSizes
.at(i
);
457 newSplitterSizes
[i
] = 0;
458 widgetsToRemove
.emplace_back(m_splitter
->widget(i
));
461 if (expansionWidthNeeded
> 1 && indexOfNonExpandingContainer
!= -1) {
462 Q_ASSERT(m_splitViewEnabled
);
463 newSplitterSizes
[indexOfNonExpandingContainer
] -= expansionWidthNeeded
;
465 newSplitterSizes
[indexOfExpandingContainer
] = value
.toInt();
466 m_splitter
->setSizes(newSplitterSizes
);
467 while (!widgetsToRemove
.empty()) {
468 widgetsToRemove
.back()->close();
469 widgetsToRemove
.back()->deleteLater();
470 widgetsToRemove
.pop_back();
475 void DolphinTabPage::slotViewActivated()
477 const DolphinView
* oldActiveView
= activeViewContainer()->view();
479 // Set the view, which was active before, to inactive
480 // and update the active view type, if tab is active
482 if (m_splitViewEnabled
) {
483 activeViewContainer()->setActive(false);
484 m_primaryViewActive
= !m_primaryViewActive
;
486 m_primaryViewActive
= true;
487 if (m_secondaryViewContainer
) {
488 m_secondaryViewContainer
->setActive(false);
493 const DolphinView
* newActiveView
= activeViewContainer()->view();
495 if (newActiveView
== oldActiveView
) {
499 disconnect(oldActiveView
, &DolphinView::urlChanged
,
500 this, &DolphinTabPage::activeViewUrlChanged
);
501 disconnect(oldActiveView
, &DolphinView::redirection
,
502 this, &DolphinTabPage::slotViewUrlRedirection
);
503 connect(newActiveView
, &DolphinView::urlChanged
,
504 this, &DolphinTabPage::activeViewUrlChanged
);
505 connect(newActiveView
, &DolphinView::redirection
,
506 this, &DolphinTabPage::slotViewUrlRedirection
);
507 Q_EMIT
activeViewChanged(activeViewContainer());
508 Q_EMIT
activeViewUrlChanged(activeViewContainer()->url());
511 void DolphinTabPage::slotViewUrlRedirection(const QUrl
& oldUrl
, const QUrl
& newUrl
)
515 Q_EMIT
activeViewUrlChanged(newUrl
);
518 void DolphinTabPage::switchActiveView()
520 if (!m_splitViewEnabled
) {
523 if (m_primaryViewActive
) {
524 m_secondaryViewContainer
->setActive(true);
526 m_primaryViewContainer
->setActive(true);
530 DolphinViewContainer
* DolphinTabPage::createViewContainer(const QUrl
& url
) const
532 DolphinViewContainer
* container
= new DolphinViewContainer(url
, m_splitter
);
533 container
->setActive(false);
535 const DolphinView
* view
= container
->view();
536 connect(view
, &DolphinView::activated
,
537 this, &DolphinTabPage::slotViewActivated
);
539 connect(view
, &DolphinView::toggleActiveViewRequested
,
540 this, &DolphinTabPage::switchActiveView
);
545 void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer
*expandingContainer
)
547 Q_CHECK_PTR(expandingContainer
);
548 Q_ASSERT(expandingContainer
== m_primaryViewContainer
||
549 expandingContainer
== m_secondaryViewContainer
);
550 m_expandingContainer
= expandingContainer
;
552 m_expandViewAnimation
= new QVariantAnimation(m_splitter
);
553 m_expandViewAnimation
->setDuration(2 *
554 style()->styleHint(QStyle::SH_Widget_Animation_Duration
, nullptr, this) *
555 GlobalConfig::animationDurationFactor());
556 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
557 m_splitter
->widget(i
)->setMinimumWidth(1);
559 connect(m_expandViewAnimation
, &QAbstractAnimation::finished
,
560 this, &DolphinTabPage::slotAnimationFinished
);
561 connect(m_expandViewAnimation
, &QVariantAnimation::valueChanged
,
562 this, &DolphinTabPage::slotAnimationValueChanged
);
564 m_expandViewAnimation
->setStartValue(expandingContainer
->width());
565 if (m_splitViewEnabled
) { // A new viewContainer is being opened.
566 m_expandViewAnimation
->setEndValue(m_splitter
->width() / 2);
567 m_expandViewAnimation
->setEasingCurve(QEasingCurve::OutCubic
);
568 } else { // A viewContainer is being closed.
569 m_expandViewAnimation
->setEndValue(m_splitter
->width());
570 m_expandViewAnimation
->setEasingCurve(QEasingCurve::InCubic
);
572 m_expandViewAnimation
->start(QAbstractAnimation::DeleteWhenStopped
);