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>
15 #include <QGridLayout>
16 #include <QWidgetAction>
19 DolphinTabPage::DolphinTabPage(const QUrl
&primaryUrl
, const QUrl
&secondaryUrl
, QWidget
* parent
) :
21 m_expandingContainer
{nullptr},
22 m_primaryViewActive(true),
23 m_splitViewEnabled(false),
26 QGridLayout
*layout
= new QGridLayout(this);
27 layout
->setSpacing(0);
28 layout
->setContentsMargins(0, 0, 0, 0);
30 m_splitter
= new DolphinTabPageSplitter(Qt::Horizontal
, this);
31 m_splitter
->setChildrenCollapsible(false);
32 connect(m_splitter
, &QSplitter::splitterMoved
,
33 this, &DolphinTabPage::splitterMoved
);
34 layout
->addWidget(m_splitter
, 1, 0);
35 layout
->setRowStretch(1, 1);
37 // Create a new primary view
38 m_primaryViewContainer
= createViewContainer(primaryUrl
);
39 connect(m_primaryViewContainer
->view(), &DolphinView::urlChanged
,
40 this, &DolphinTabPage::activeViewUrlChanged
);
41 connect(m_primaryViewContainer
->view(), &DolphinView::redirection
,
42 this, &DolphinTabPage::slotViewUrlRedirection
);
44 m_splitter
->addWidget(m_primaryViewContainer
);
45 m_primaryViewContainer
->show();
47 if (secondaryUrl
.isValid() || GeneralSettings::splitView()) {
48 // Provide a secondary view, if the given secondary url is valid or if the
49 // startup settings are set this way (use the url of the primary view).
50 m_splitViewEnabled
= true;
51 const QUrl
& url
= secondaryUrl
.isValid() ? secondaryUrl
: primaryUrl
;
52 m_secondaryViewContainer
= createViewContainer(url
);
53 m_splitter
->addWidget(m_secondaryViewContainer
);
54 m_secondaryViewContainer
->show();
57 m_primaryViewContainer
->setActive(true);
60 bool DolphinTabPage::primaryViewActive() const
62 return m_primaryViewActive
;
65 bool DolphinTabPage::splitViewEnabled() const
67 return m_splitViewEnabled
;
70 void DolphinTabPage::setSplitViewEnabled(bool enabled
, Animated animated
, const QUrl
&secondaryUrl
)
72 if (m_splitViewEnabled
!= enabled
) {
73 m_splitViewEnabled
= enabled
;
74 if (animated
== WithAnimation
&& (
75 style()->styleHint(QStyle::SH_Widget_Animation_Duration
, nullptr, this) < 1 ||
76 GlobalConfig::animationDurationFactor() <= 0.0)) {
77 animated
= WithoutAnimation
;
79 if (m_expandViewAnimation
) {
80 m_expandViewAnimation
->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped.
81 if (animated
== WithoutAnimation
) {
82 slotAnimationFinished();
87 QList
<int> splitterSizes
= m_splitter
->sizes();
88 const QUrl
& url
= (secondaryUrl
.isEmpty()) ? m_primaryViewContainer
->url() : secondaryUrl
;
89 m_secondaryViewContainer
= createViewContainer(url
);
91 auto secondaryNavigator
= m_navigatorsWidget
->secondaryUrlNavigator();
92 if (!secondaryNavigator
) {
93 m_navigatorsWidget
->createSecondaryUrlNavigator();
94 secondaryNavigator
= m_navigatorsWidget
->secondaryUrlNavigator();
96 m_secondaryViewContainer
->connectUrlNavigator(secondaryNavigator
);
97 m_navigatorsWidget
->setSecondaryNavigatorVisible(true);
98 m_navigatorsWidget
->followViewContainersGeometry(m_primaryViewContainer
,
99 m_secondaryViewContainer
);
101 m_splitter
->addWidget(m_secondaryViewContainer
);
102 m_secondaryViewContainer
->setActive(true);
104 if (animated
== WithAnimation
) {
105 m_secondaryViewContainer
->setMinimumWidth(1);
106 splitterSizes
.append(1);
107 m_splitter
->setSizes(splitterSizes
);
108 startExpandViewAnimation(m_secondaryViewContainer
);
110 m_secondaryViewContainer
->show();
112 m_navigatorsWidget
->setSecondaryNavigatorVisible(false);
113 m_secondaryViewContainer
->disconnectUrlNavigator();
115 DolphinViewContainer
* view
;
116 if (GeneralSettings::closeActiveSplitView()) {
117 view
= activeViewContainer();
118 if (m_primaryViewActive
) {
119 m_primaryViewContainer
->disconnectUrlNavigator();
120 m_secondaryViewContainer
->connectUrlNavigator(
121 m_navigatorsWidget
->primaryUrlNavigator());
123 // If the primary view is active, we have to swap the pointers
124 // because the secondary view will be the new primary view.
125 qSwap(m_primaryViewContainer
, m_secondaryViewContainer
);
126 m_primaryViewActive
= false;
129 view
= m_primaryViewActive
? m_secondaryViewContainer
: m_primaryViewContainer
;
130 if (!m_primaryViewActive
) {
131 m_primaryViewContainer
->disconnectUrlNavigator();
132 m_secondaryViewContainer
->connectUrlNavigator(
133 m_navigatorsWidget
->primaryUrlNavigator());
135 // If the secondary view is active, we have to swap the pointers
136 // because the secondary view will be the new primary view.
137 qSwap(m_primaryViewContainer
, m_secondaryViewContainer
);
138 m_primaryViewActive
= true;
141 m_primaryViewContainer
->setActive(true);
142 m_navigatorsWidget
->followViewContainersGeometry(m_primaryViewContainer
, nullptr);
144 if (animated
== WithoutAnimation
) {
148 // Kill it but keep it as a zombie for the closing animation.
149 m_secondaryViewContainer
= nullptr;
150 view
->blockSignals(true);
151 view
->view()->blockSignals(true);
152 view
->setDisabled(true);
153 startExpandViewAnimation(m_primaryViewContainer
);
159 DolphinViewContainer
* DolphinTabPage::primaryViewContainer() const
161 return m_primaryViewContainer
;
164 DolphinViewContainer
* DolphinTabPage::secondaryViewContainer() const
166 return m_secondaryViewContainer
;
169 DolphinViewContainer
* DolphinTabPage::activeViewContainer() const
171 return m_primaryViewActive
? m_primaryViewContainer
:
172 m_secondaryViewContainer
;
175 KFileItemList
DolphinTabPage::selectedItems() const
177 KFileItemList items
= m_primaryViewContainer
->view()->selectedItems();
178 if (m_splitViewEnabled
) {
179 items
+= m_secondaryViewContainer
->view()->selectedItems();
184 int DolphinTabPage::selectedItemsCount() const
186 int selectedItemsCount
= m_primaryViewContainer
->view()->selectedItemsCount();
187 if (m_splitViewEnabled
) {
188 selectedItemsCount
+= m_secondaryViewContainer
->view()->selectedItemsCount();
190 return selectedItemsCount
;
193 void DolphinTabPage::connectNavigators(DolphinNavigatorsWidgetAction
*navigatorsWidget
)
195 insertNavigatorsWidget(navigatorsWidget
);
196 m_navigatorsWidget
= navigatorsWidget
;
197 auto primaryNavigator
= navigatorsWidget
->primaryUrlNavigator();
198 m_primaryViewContainer
->connectUrlNavigator(primaryNavigator
);
199 if (m_splitViewEnabled
) {
200 auto secondaryNavigator
= navigatorsWidget
->secondaryUrlNavigator();
201 m_secondaryViewContainer
->connectUrlNavigator(secondaryNavigator
);
203 m_navigatorsWidget
->followViewContainersGeometry(m_primaryViewContainer
,
204 m_secondaryViewContainer
);
207 void DolphinTabPage::disconnectNavigators()
209 m_navigatorsWidget
= nullptr;
210 m_primaryViewContainer
->disconnectUrlNavigator();
211 if (m_splitViewEnabled
) {
212 m_secondaryViewContainer
->disconnectUrlNavigator();
216 void DolphinTabPage::insertNavigatorsWidget(DolphinNavigatorsWidgetAction
* navigatorsWidget
)
218 QGridLayout
*gridLayout
= static_cast<QGridLayout
*>(layout());
219 if (navigatorsWidget
->isInToolbar()) {
220 gridLayout
->setRowMinimumHeight(0, 0);
222 // We set a row minimum height, so the height does not visibly change whenever
223 // navigatorsWidget is inserted which happens every time the current tab is changed.
224 gridLayout
->setRowMinimumHeight(0, navigatorsWidget
->primaryUrlNavigator()->height());
225 gridLayout
->addWidget(navigatorsWidget
->requestWidget(this), 0, 0);
229 void DolphinTabPage::markUrlsAsSelected(const QList
<QUrl
>& urls
)
231 m_primaryViewContainer
->view()->markUrlsAsSelected(urls
);
232 if (m_splitViewEnabled
) {
233 m_secondaryViewContainer
->view()->markUrlsAsSelected(urls
);
237 void DolphinTabPage::markUrlAsCurrent(const QUrl
& url
)
239 m_primaryViewContainer
->view()->markUrlAsCurrent(url
);
240 if (m_splitViewEnabled
) {
241 m_secondaryViewContainer
->view()->markUrlAsCurrent(url
);
245 void DolphinTabPage::refreshViews()
247 m_primaryViewContainer
->readSettings();
248 if (m_splitViewEnabled
) {
249 m_secondaryViewContainer
->readSettings();
253 QByteArray
DolphinTabPage::saveState() const
256 QDataStream
stream(&state
, QIODevice::WriteOnly
);
258 stream
<< quint32(2); // Tab state version
260 stream
<< m_splitViewEnabled
;
262 stream
<< m_primaryViewContainer
->url();
263 stream
<< m_primaryViewContainer
->urlNavigatorInternalWithHistory()->isUrlEditable();
264 m_primaryViewContainer
->view()->saveState(stream
);
266 if (m_splitViewEnabled
) {
267 stream
<< m_secondaryViewContainer
->url();
268 stream
<< m_secondaryViewContainer
->urlNavigatorInternalWithHistory()->isUrlEditable();
269 m_secondaryViewContainer
->view()->saveState(stream
);
272 stream
<< m_primaryViewActive
;
273 stream
<< m_splitter
->saveState();
278 void DolphinTabPage::restoreState(const QByteArray
& state
)
280 if (state
.isEmpty()) {
284 QByteArray sd
= state
;
285 QDataStream
stream(&sd
, QIODevice::ReadOnly
);
287 // Read the version number of the tab state and check if the version is supported.
291 // The version of the tab state isn't supported, we can't restore it.
295 bool isSplitViewEnabled
= false;
296 stream
>> isSplitViewEnabled
;
297 setSplitViewEnabled(isSplitViewEnabled
, WithoutAnimation
);
300 stream
>> primaryUrl
;
301 m_primaryViewContainer
->setUrl(primaryUrl
);
302 bool primaryUrlEditable
;
303 stream
>> primaryUrlEditable
;
304 m_primaryViewContainer
->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable
);
305 m_primaryViewContainer
->view()->restoreState(stream
);
307 if (isSplitViewEnabled
) {
309 stream
>> secondaryUrl
;
310 m_secondaryViewContainer
->setUrl(secondaryUrl
);
311 bool secondaryUrlEditable
;
312 stream
>> secondaryUrlEditable
;
313 m_secondaryViewContainer
->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable
);
314 m_secondaryViewContainer
->view()->restoreState(stream
);
317 stream
>> m_primaryViewActive
;
318 if (m_primaryViewActive
) {
319 m_primaryViewContainer
->setActive(true);
321 Q_ASSERT(m_splitViewEnabled
);
322 m_secondaryViewContainer
->setActive(true);
325 QByteArray splitterState
;
326 stream
>> splitterState
;
327 m_splitter
->restoreState(splitterState
);
330 void DolphinTabPage::setActive(bool active
)
335 // we should bypass changing active view in split mode
336 m_active
= !m_splitViewEnabled
;
338 // we want view to fire activated when goes from false to true
339 activeViewContainer()->setActive(active
);
342 void DolphinTabPage::slotAnimationFinished()
344 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
345 QWidget
*viewContainer
= m_splitter
->widget(i
);
346 if (viewContainer
!= m_primaryViewContainer
&&
347 viewContainer
!= m_secondaryViewContainer
) {
348 viewContainer
->close();
349 viewContainer
->deleteLater();
352 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
353 QWidget
*viewContainer
= m_splitter
->widget(i
);
354 viewContainer
->setMinimumWidth(viewContainer
->minimumSizeHint().width());
356 m_expandingContainer
= nullptr;
359 void DolphinTabPage::slotAnimationValueChanged(const QVariant
& value
)
361 Q_CHECK_PTR(m_expandingContainer
);
362 const int indexOfExpandingContainer
= m_splitter
->indexOf(m_expandingContainer
);
363 int indexOfNonExpandingContainer
= -1;
364 if (m_expandingContainer
== m_primaryViewContainer
) {
365 indexOfNonExpandingContainer
= m_splitter
->indexOf(m_secondaryViewContainer
);
367 indexOfNonExpandingContainer
= m_splitter
->indexOf(m_primaryViewContainer
);
369 std::vector
<QWidget
*> widgetsToRemove
;
370 const QList
<int> oldSplitterSizes
= m_splitter
->sizes();
371 QList
<int> newSplitterSizes
{oldSplitterSizes
};
372 int expansionWidthNeeded
= value
.toInt() - oldSplitterSizes
.at(indexOfExpandingContainer
);
374 // Reduce the size of the other widgets to make space for the expandingContainer.
375 for (int i
= m_splitter
->count() - 1; i
>= 0; --i
) {
376 if (m_splitter
->widget(i
) == m_primaryViewContainer
||
377 m_splitter
->widget(i
) == m_secondaryViewContainer
) {
380 newSplitterSizes
[i
] = oldSplitterSizes
.at(i
) - expansionWidthNeeded
;
381 expansionWidthNeeded
= 0;
382 if (indexOfNonExpandingContainer
!= -1) {
383 // Make sure every zombie container is at least slightly reduced in size
384 // so it doesn't seem like they are here to stay.
385 newSplitterSizes
[i
]--;
386 newSplitterSizes
[indexOfNonExpandingContainer
]++;
388 if (newSplitterSizes
.at(i
) <= 0) {
389 expansionWidthNeeded
-= newSplitterSizes
.at(i
);
390 newSplitterSizes
[i
] = 0;
391 widgetsToRemove
.emplace_back(m_splitter
->widget(i
));
394 if (expansionWidthNeeded
> 1 && indexOfNonExpandingContainer
!= -1) {
395 Q_ASSERT(m_splitViewEnabled
);
396 newSplitterSizes
[indexOfNonExpandingContainer
] -= expansionWidthNeeded
;
398 newSplitterSizes
[indexOfExpandingContainer
] = value
.toInt();
399 m_splitter
->setSizes(newSplitterSizes
);
400 while (!widgetsToRemove
.empty()) {
401 widgetsToRemove
.back()->close();
402 widgetsToRemove
.back()->deleteLater();
403 widgetsToRemove
.pop_back();
408 void DolphinTabPage::slotViewActivated()
410 const DolphinView
* oldActiveView
= activeViewContainer()->view();
412 // Set the view, which was active before, to inactive
413 // and update the active view type, if tab is active
415 if (m_splitViewEnabled
) {
416 activeViewContainer()->setActive(false);
417 m_primaryViewActive
= !m_primaryViewActive
;
419 m_primaryViewActive
= true;
420 if (m_secondaryViewContainer
) {
421 m_secondaryViewContainer
->setActive(false);
426 const DolphinView
* newActiveView
= activeViewContainer()->view();
428 if (newActiveView
== oldActiveView
) {
432 disconnect(oldActiveView
, &DolphinView::urlChanged
,
433 this, &DolphinTabPage::activeViewUrlChanged
);
434 disconnect(oldActiveView
, &DolphinView::redirection
,
435 this, &DolphinTabPage::slotViewUrlRedirection
);
436 connect(newActiveView
, &DolphinView::urlChanged
,
437 this, &DolphinTabPage::activeViewUrlChanged
);
438 connect(newActiveView
, &DolphinView::redirection
,
439 this, &DolphinTabPage::slotViewUrlRedirection
);
440 Q_EMIT
activeViewChanged(activeViewContainer());
441 Q_EMIT
activeViewUrlChanged(activeViewContainer()->url());
444 void DolphinTabPage::slotViewUrlRedirection(const QUrl
& oldUrl
, const QUrl
& newUrl
)
448 Q_EMIT
activeViewUrlChanged(newUrl
);
451 void DolphinTabPage::switchActiveView()
453 if (!m_splitViewEnabled
) {
456 if (m_primaryViewActive
) {
457 m_secondaryViewContainer
->setActive(true);
459 m_primaryViewContainer
->setActive(true);
463 DolphinViewContainer
* DolphinTabPage::createViewContainer(const QUrl
& url
) const
465 DolphinViewContainer
* container
= new DolphinViewContainer(url
, m_splitter
);
466 container
->setActive(false);
468 const DolphinView
* view
= container
->view();
469 connect(view
, &DolphinView::activated
,
470 this, &DolphinTabPage::slotViewActivated
);
472 connect(view
, &DolphinView::toggleActiveViewRequested
,
473 this, &DolphinTabPage::switchActiveView
);
478 void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer
*expandingContainer
)
480 Q_CHECK_PTR(expandingContainer
);
481 Q_ASSERT(expandingContainer
== m_primaryViewContainer
||
482 expandingContainer
== m_secondaryViewContainer
);
483 m_expandingContainer
= expandingContainer
;
485 m_expandViewAnimation
= new QVariantAnimation(m_splitter
);
486 m_expandViewAnimation
->setDuration(2 *
487 style()->styleHint(QStyle::SH_Widget_Animation_Duration
, nullptr, this) *
488 GlobalConfig::animationDurationFactor());
489 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
490 m_splitter
->widget(i
)->setMinimumWidth(1);
492 connect(m_expandViewAnimation
, &QAbstractAnimation::finished
,
493 this, &DolphinTabPage::slotAnimationFinished
);
494 connect(m_expandViewAnimation
, &QVariantAnimation::valueChanged
,
495 this, &DolphinTabPage::slotAnimationValueChanged
);
497 m_expandViewAnimation
->setStartValue(expandingContainer
->width());
498 if (m_splitViewEnabled
) { // A new viewContainer is being opened.
499 m_expandViewAnimation
->setEndValue(m_splitter
->width() / 2);
500 m_expandViewAnimation
->setEasingCurve(QEasingCurve::OutCubic
);
501 } else { // A viewContainer is being closed.
502 m_expandViewAnimation
->setEndValue(m_splitter
->width());
503 m_expandViewAnimation
->setEasingCurve(QEasingCurve::InCubic
);
505 m_expandViewAnimation
->start(QAbstractAnimation::DeleteWhenStopped
);
508 DolphinTabPageSplitterHandle::DolphinTabPageSplitterHandle(Qt::Orientation orientation
, QSplitter
*parent
)
509 : QSplitterHandle(orientation
, parent
)
510 , m_mouseReleaseWasReceived(false)
513 bool DolphinTabPageSplitterHandle::event(QEvent
*event
)
515 switch (event
->type()) {
516 case QEvent::MouseButtonPress
:
517 m_mouseReleaseWasReceived
= false;
519 case QEvent::MouseButtonRelease
:
520 if (m_mouseReleaseWasReceived
) {
521 resetSplitterSizes();
523 m_mouseReleaseWasReceived
= !m_mouseReleaseWasReceived
;
525 case QEvent::MouseButtonDblClick
:
526 m_mouseReleaseWasReceived
= false;
527 resetSplitterSizes();
533 return QSplitterHandle::event(event
);
536 void DolphinTabPageSplitterHandle::resetSplitterSizes()
538 QList
<int> splitterSizes
= splitter()->sizes();
539 std::fill(splitterSizes
.begin(), splitterSizes
.end(), 0);
540 splitter()->setSizes(splitterSizes
);
543 DolphinTabPageSplitter::DolphinTabPageSplitter(Qt::Orientation orientation
, QWidget
*parent
)
544 : QSplitter(orientation
, parent
)
547 QSplitterHandle
* DolphinTabPageSplitter::createHandle()
549 return new DolphinTabPageSplitterHandle(orientation(), this);