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>
18 DolphinTabPage::DolphinTabPage(const QUrl
&primaryUrl
, const QUrl
&secondaryUrl
, QWidget
*parent
)
20 , m_expandingContainer
{nullptr}
21 , m_primaryViewActive(true)
22 , m_splitViewEnabled(false)
25 QGridLayout
*layout
= new QGridLayout(this);
26 layout
->setSpacing(0);
27 layout
->setContentsMargins(0, 0, 0, 0);
29 m_splitter
= new DolphinTabPageSplitter(Qt::Horizontal
, this);
30 m_splitter
->setChildrenCollapsible(false);
31 connect(m_splitter
, &QSplitter::splitterMoved
, this, &DolphinTabPage::splitterMoved
);
32 layout
->addWidget(m_splitter
, 1, 0);
33 layout
->setRowStretch(1, 1);
35 // Create a new primary view
36 m_primaryViewContainer
= createViewContainer(primaryUrl
);
37 connect(m_primaryViewContainer
->view(), &DolphinView::urlChanged
, this, &DolphinTabPage::activeViewUrlChanged
);
38 connect(m_primaryViewContainer
->view(), &DolphinView::redirection
, this, &DolphinTabPage::slotViewUrlRedirection
);
40 m_splitter
->addWidget(m_primaryViewContainer
);
41 m_primaryViewContainer
->show();
43 if (secondaryUrl
.isValid() || GeneralSettings::splitView()) {
44 // Provide a secondary view, if the given secondary url is valid or if the
45 // startup settings are set this way (use the url of the primary view).
46 m_splitViewEnabled
= true;
47 const QUrl
&url
= secondaryUrl
.isValid() ? secondaryUrl
: primaryUrl
;
48 m_secondaryViewContainer
= createViewContainer(url
);
49 connect(m_secondaryViewContainer
->view(), &DolphinView::redirection
, this, &DolphinTabPage::slotViewUrlRedirection
);
50 m_splitter
->addWidget(m_secondaryViewContainer
);
51 m_secondaryViewContainer
->show();
54 m_primaryViewContainer
->setActive(true);
57 bool DolphinTabPage::primaryViewActive() const
59 return m_primaryViewActive
;
62 bool DolphinTabPage::splitViewEnabled() const
64 return m_splitViewEnabled
;
67 void DolphinTabPage::setSplitViewEnabled(bool enabled
, Animated animated
, const QUrl
&secondaryUrl
)
69 if (m_splitViewEnabled
!= enabled
) {
70 m_splitViewEnabled
= enabled
;
71 if (animated
== WithAnimation
72 && (style()->styleHint(QStyle::SH_Widget_Animation_Duration
, nullptr, this) < 1 || GlobalConfig::animationDurationFactor() <= 0.0)) {
73 animated
= WithoutAnimation
;
75 if (m_expandViewAnimation
) {
76 m_expandViewAnimation
->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped.
77 if (animated
== WithoutAnimation
) {
78 slotAnimationFinished();
83 QList
<int> splitterSizes
= m_splitter
->sizes();
84 const QUrl
&url
= (secondaryUrl
.isEmpty()) ? m_primaryViewContainer
->url() : secondaryUrl
;
85 m_secondaryViewContainer
= createViewContainer(url
);
87 auto secondaryNavigator
= m_navigatorsWidget
->secondaryUrlNavigator();
88 if (!secondaryNavigator
) {
89 m_navigatorsWidget
->createSecondaryUrlNavigator();
90 secondaryNavigator
= m_navigatorsWidget
->secondaryUrlNavigator();
92 m_secondaryViewContainer
->connectUrlNavigator(secondaryNavigator
);
93 connect(m_secondaryViewContainer
->view(), &DolphinView::redirection
, this, &DolphinTabPage::slotViewUrlRedirection
);
94 m_navigatorsWidget
->setSecondaryNavigatorVisible(true);
95 m_navigatorsWidget
->followViewContainersGeometry(m_primaryViewContainer
, m_secondaryViewContainer
);
97 m_splitter
->addWidget(m_secondaryViewContainer
);
98 m_secondaryViewContainer
->setActive(true);
100 if (animated
== WithAnimation
) {
101 m_secondaryViewContainer
->setMinimumWidth(1);
102 splitterSizes
.append(1);
103 m_splitter
->setSizes(splitterSizes
);
104 startExpandViewAnimation(m_secondaryViewContainer
);
106 m_secondaryViewContainer
->show();
108 m_navigatorsWidget
->setSecondaryNavigatorVisible(false);
109 m_secondaryViewContainer
->disconnectUrlNavigator();
110 disconnect(m_secondaryViewContainer
->view(), &DolphinView::redirection
, this, &DolphinTabPage::slotViewUrlRedirection
);
112 DolphinViewContainer
*view
;
113 if (GeneralSettings::closeActiveSplitView()) {
114 view
= activeViewContainer();
115 if (m_primaryViewActive
) {
116 m_primaryViewContainer
->disconnectUrlNavigator();
117 m_secondaryViewContainer
->connectUrlNavigator(m_navigatorsWidget
->primaryUrlNavigator());
119 // If the primary view is active, we have to swap the pointers
120 // because the secondary view will be the new primary view.
121 std::swap(m_primaryViewContainer
, m_secondaryViewContainer
);
122 m_primaryViewActive
= false;
125 view
= m_primaryViewActive
? m_secondaryViewContainer
: m_primaryViewContainer
;
126 if (!m_primaryViewActive
) {
127 m_primaryViewContainer
->disconnectUrlNavigator();
128 m_secondaryViewContainer
->connectUrlNavigator(m_navigatorsWidget
->primaryUrlNavigator());
130 // If the secondary view is active, we have to swap the pointers
131 // because the secondary view will be the new primary view.
132 std::swap(m_primaryViewContainer
, m_secondaryViewContainer
);
133 m_primaryViewActive
= true;
136 m_primaryViewContainer
->setActive(true);
137 m_navigatorsWidget
->followViewContainersGeometry(m_primaryViewContainer
, nullptr);
139 if (animated
== WithoutAnimation
) {
143 // Kill it but keep it as a zombie for the closing animation.
144 m_secondaryViewContainer
= nullptr;
145 view
->blockSignals(true);
146 view
->view()->blockSignals(true);
147 view
->setDisabled(true);
148 startExpandViewAnimation(m_primaryViewContainer
);
151 m_primaryViewContainer
->slotSplitTabDisabled();
156 DolphinViewContainer
*DolphinTabPage::primaryViewContainer() const
158 return m_primaryViewContainer
;
161 DolphinViewContainer
*DolphinTabPage::secondaryViewContainer() const
163 return m_secondaryViewContainer
;
166 DolphinViewContainer
*DolphinTabPage::activeViewContainer() const
168 return m_primaryViewActive
? m_primaryViewContainer
: m_secondaryViewContainer
;
171 DolphinViewContainer
*DolphinTabPage::inactiveViewContainer() const
173 if (!splitViewEnabled()) {
177 return primaryViewActive() ? secondaryViewContainer() : primaryViewContainer();
180 KFileItemList
DolphinTabPage::selectedItems() const
182 KFileItemList items
= m_primaryViewContainer
->view()->selectedItems();
183 if (m_splitViewEnabled
) {
184 items
+= m_secondaryViewContainer
->view()->selectedItems();
189 int DolphinTabPage::selectedItemsCount() const
191 int selectedItemsCount
= m_primaryViewContainer
->view()->selectedItemsCount();
192 if (m_splitViewEnabled
) {
193 selectedItemsCount
+= m_secondaryViewContainer
->view()->selectedItemsCount();
195 return selectedItemsCount
;
198 void DolphinTabPage::connectNavigators(DolphinNavigatorsWidgetAction
*navigatorsWidget
)
200 insertNavigatorsWidget(navigatorsWidget
);
201 m_navigatorsWidget
= navigatorsWidget
;
202 auto primaryNavigator
= navigatorsWidget
->primaryUrlNavigator();
203 m_primaryViewContainer
->connectUrlNavigator(primaryNavigator
);
204 if (m_splitViewEnabled
) {
205 auto secondaryNavigator
= navigatorsWidget
->secondaryUrlNavigator();
206 m_secondaryViewContainer
->connectUrlNavigator(secondaryNavigator
);
208 m_navigatorsWidget
->followViewContainersGeometry(m_primaryViewContainer
, m_secondaryViewContainer
);
211 void DolphinTabPage::disconnectNavigators()
213 m_navigatorsWidget
= nullptr;
214 m_primaryViewContainer
->disconnectUrlNavigator();
215 if (m_splitViewEnabled
) {
216 m_secondaryViewContainer
->disconnectUrlNavigator();
220 void DolphinTabPage::insertNavigatorsWidget(DolphinNavigatorsWidgetAction
*navigatorsWidget
)
222 QGridLayout
*gridLayout
= static_cast<QGridLayout
*>(layout());
223 if (navigatorsWidget
->isInToolbar()) {
224 gridLayout
->setRowMinimumHeight(0, 0);
226 // We set a row minimum height, so the height does not visibly change whenever
227 // navigatorsWidget is inserted which happens every time the current tab is changed.
228 gridLayout
->setRowMinimumHeight(0, navigatorsWidget
->primaryUrlNavigator()->height());
229 gridLayout
->addWidget(navigatorsWidget
->requestWidget(this), 0, 0);
233 void DolphinTabPage::markUrlsAsSelected(const QList
<QUrl
> &urls
)
235 m_primaryViewContainer
->view()->markUrlsAsSelected(urls
);
236 if (m_splitViewEnabled
) {
237 m_secondaryViewContainer
->view()->markUrlsAsSelected(urls
);
241 void DolphinTabPage::markUrlAsCurrent(const QUrl
&url
)
243 m_primaryViewContainer
->view()->markUrlAsCurrent(url
);
244 if (m_splitViewEnabled
) {
245 m_secondaryViewContainer
->view()->markUrlAsCurrent(url
);
249 void DolphinTabPage::refreshViews()
251 m_primaryViewContainer
->readSettings();
252 if (m_splitViewEnabled
) {
253 m_secondaryViewContainer
->readSettings();
257 QByteArray
DolphinTabPage::saveState() const
260 QDataStream
stream(&state
, QIODevice::WriteOnly
);
262 stream
<< quint32(2); // Tab state version
264 stream
<< m_splitViewEnabled
;
266 stream
<< m_primaryViewContainer
->url();
267 stream
<< m_primaryViewContainer
->urlNavigatorInternalWithHistory()->isUrlEditable();
268 m_primaryViewContainer
->view()->saveState(stream
);
270 if (m_splitViewEnabled
) {
271 stream
<< m_secondaryViewContainer
->url();
272 stream
<< m_secondaryViewContainer
->urlNavigatorInternalWithHistory()->isUrlEditable();
273 m_secondaryViewContainer
->view()->saveState(stream
);
276 stream
<< m_primaryViewActive
;
277 stream
<< m_splitter
->saveState();
282 void DolphinTabPage::restoreState(const QByteArray
&state
)
284 if (state
.isEmpty()) {
288 QByteArray sd
= state
;
289 QDataStream
stream(&sd
, QIODevice::ReadOnly
);
291 // Read the version number of the tab state and check if the version is supported.
295 // The version of the tab state isn't supported, we can't restore it.
299 bool isSplitViewEnabled
= false;
300 stream
>> isSplitViewEnabled
;
301 setSplitViewEnabled(isSplitViewEnabled
, WithoutAnimation
);
304 stream
>> primaryUrl
;
305 m_primaryViewContainer
->setUrl(primaryUrl
);
306 bool primaryUrlEditable
;
307 stream
>> primaryUrlEditable
;
308 m_primaryViewContainer
->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable
);
309 m_primaryViewContainer
->view()->restoreState(stream
);
311 if (isSplitViewEnabled
) {
313 stream
>> secondaryUrl
;
314 m_secondaryViewContainer
->setUrl(secondaryUrl
);
315 bool secondaryUrlEditable
;
316 stream
>> secondaryUrlEditable
;
317 m_secondaryViewContainer
->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable
);
318 m_secondaryViewContainer
->view()->restoreState(stream
);
321 stream
>> m_primaryViewActive
;
322 if (m_primaryViewActive
) {
323 m_primaryViewContainer
->setActive(true);
325 Q_ASSERT(m_splitViewEnabled
);
326 m_secondaryViewContainer
->setActive(true);
329 QByteArray splitterState
;
330 stream
>> splitterState
;
331 m_splitter
->restoreState(splitterState
);
334 void DolphinTabPage::setActive(bool active
)
339 // we should bypass changing active view in split mode
340 m_active
= !m_splitViewEnabled
;
342 // we want view to fire activated when goes from false to true
343 activeViewContainer()->setActive(active
);
346 void DolphinTabPage::setTitle(const QString
&name
)
348 m_title
= QString(name
);
351 QString
DolphinTabPage::title()
356 void DolphinTabPage::slotAnimationFinished()
358 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
359 QWidget
*viewContainer
= m_splitter
->widget(i
);
360 if (viewContainer
!= m_primaryViewContainer
&& viewContainer
!= m_secondaryViewContainer
) {
361 viewContainer
->close();
362 viewContainer
->deleteLater();
365 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
366 QWidget
*viewContainer
= m_splitter
->widget(i
);
367 viewContainer
->setMinimumWidth(viewContainer
->minimumSizeHint().width());
369 m_expandingContainer
= nullptr;
372 void DolphinTabPage::slotAnimationValueChanged(const QVariant
&value
)
374 Q_CHECK_PTR(m_expandingContainer
);
375 const int indexOfExpandingContainer
= m_splitter
->indexOf(m_expandingContainer
);
376 int indexOfNonExpandingContainer
= -1;
377 if (m_expandingContainer
== m_primaryViewContainer
) {
378 indexOfNonExpandingContainer
= m_splitter
->indexOf(m_secondaryViewContainer
);
380 indexOfNonExpandingContainer
= m_splitter
->indexOf(m_primaryViewContainer
);
382 std::vector
<QWidget
*> widgetsToRemove
;
383 const QList
<int> oldSplitterSizes
= m_splitter
->sizes();
384 QList
<int> newSplitterSizes
{oldSplitterSizes
};
385 int expansionWidthNeeded
= value
.toInt() - oldSplitterSizes
.at(indexOfExpandingContainer
);
387 // Reduce the size of the other widgets to make space for the expandingContainer.
388 for (int i
= m_splitter
->count() - 1; i
>= 0; --i
) {
389 if (m_splitter
->widget(i
) == m_primaryViewContainer
|| m_splitter
->widget(i
) == m_secondaryViewContainer
) {
392 newSplitterSizes
[i
] = oldSplitterSizes
.at(i
) - expansionWidthNeeded
;
393 expansionWidthNeeded
= 0;
394 if (indexOfNonExpandingContainer
!= -1) {
395 // Make sure every zombie container is at least slightly reduced in size
396 // so it doesn't seem like they are here to stay.
397 newSplitterSizes
[i
]--;
398 newSplitterSizes
[indexOfNonExpandingContainer
]++;
400 if (newSplitterSizes
.at(i
) <= 0) {
401 expansionWidthNeeded
-= newSplitterSizes
.at(i
);
402 newSplitterSizes
[i
] = 0;
403 widgetsToRemove
.emplace_back(m_splitter
->widget(i
));
406 if (expansionWidthNeeded
> 1 && indexOfNonExpandingContainer
!= -1) {
407 Q_ASSERT(m_splitViewEnabled
);
408 newSplitterSizes
[indexOfNonExpandingContainer
] -= expansionWidthNeeded
;
410 newSplitterSizes
[indexOfExpandingContainer
] = value
.toInt();
411 m_splitter
->setSizes(newSplitterSizes
);
412 while (!widgetsToRemove
.empty()) {
413 widgetsToRemove
.back()->close();
414 widgetsToRemove
.back()->deleteLater();
415 widgetsToRemove
.pop_back();
419 void DolphinTabPage::slotViewActivated()
421 const DolphinView
*oldActiveView
= activeViewContainer()->view();
423 // Set the view, which was active before, to inactive
424 // and update the active view type, if tab is active
426 if (m_splitViewEnabled
) {
427 activeViewContainer()->setActive(false);
428 m_primaryViewActive
= !m_primaryViewActive
;
430 m_primaryViewActive
= true;
431 if (m_secondaryViewContainer
) {
432 m_secondaryViewContainer
->setActive(false);
437 const DolphinView
*newActiveView
= activeViewContainer()->view();
439 if (newActiveView
== oldActiveView
) {
443 disconnect(oldActiveView
, &DolphinView::urlChanged
, this, &DolphinTabPage::activeViewUrlChanged
);
444 connect(newActiveView
, &DolphinView::urlChanged
, this, &DolphinTabPage::activeViewUrlChanged
);
445 Q_EMIT
activeViewChanged(activeViewContainer());
446 Q_EMIT
activeViewUrlChanged(activeViewContainer()->url());
449 void DolphinTabPage::slotViewUrlRedirection(const QUrl
&oldUrl
, const QUrl
&newUrl
)
451 // Make sure the url of the view is updated. BUG:496414
452 if (splitViewEnabled()) {
453 if (primaryViewContainer()->view()->url() == oldUrl
) {
454 primaryViewContainer()->view()->setUrl(newUrl
);
456 if (secondaryViewContainer()->view()->url() == oldUrl
) {
457 secondaryViewContainer()->view()->setUrl(newUrl
);
460 activeViewContainer()->view()->setUrl(newUrl
);
462 Q_EMIT
activeViewUrlChanged(newUrl
);
465 void DolphinTabPage::switchActiveView()
467 if (!m_splitViewEnabled
) {
470 if (m_primaryViewActive
) {
471 m_secondaryViewContainer
->setActive(true);
473 m_primaryViewContainer
->setActive(true);
477 DolphinViewContainer
*DolphinTabPage::createViewContainer(const QUrl
&url
) const
479 DolphinViewContainer
*container
= new DolphinViewContainer(url
, m_splitter
);
480 container
->setActive(false);
482 const DolphinView
*view
= container
->view();
483 connect(view
, &DolphinView::activated
, this, &DolphinTabPage::slotViewActivated
);
485 connect(view
, &DolphinView::toggleActiveViewRequested
, this, &DolphinTabPage::switchActiveView
);
490 void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer
*expandingContainer
)
492 Q_CHECK_PTR(expandingContainer
);
493 Q_ASSERT(expandingContainer
== m_primaryViewContainer
|| expandingContainer
== m_secondaryViewContainer
);
494 m_expandingContainer
= expandingContainer
;
496 m_expandViewAnimation
= new QVariantAnimation(m_splitter
);
497 m_expandViewAnimation
->setDuration(2 * style()->styleHint(QStyle::SH_Widget_Animation_Duration
, nullptr, this) * GlobalConfig::animationDurationFactor());
498 for (int i
= 0; i
< m_splitter
->count(); ++i
) {
499 m_splitter
->widget(i
)->setMinimumWidth(1);
501 connect(m_expandViewAnimation
, &QAbstractAnimation::finished
, this, &DolphinTabPage::slotAnimationFinished
);
502 connect(m_expandViewAnimation
, &QVariantAnimation::valueChanged
, this, &DolphinTabPage::slotAnimationValueChanged
);
504 m_expandViewAnimation
->setStartValue(expandingContainer
->width());
505 if (m_splitViewEnabled
) { // A new viewContainer is being opened.
506 m_expandViewAnimation
->setEndValue(m_splitter
->width() / 2);
507 m_expandViewAnimation
->setEasingCurve(QEasingCurve::OutCubic
);
508 } else { // A viewContainer is being closed.
509 m_expandViewAnimation
->setEndValue(m_splitter
->width());
510 m_expandViewAnimation
->setEasingCurve(QEasingCurve::InCubic
);
512 m_expandViewAnimation
->start(QAbstractAnimation::DeleteWhenStopped
);
515 DolphinTabPageSplitterHandle::DolphinTabPageSplitterHandle(Qt::Orientation orientation
, QSplitter
*parent
)
516 : QSplitterHandle(orientation
, parent
)
517 , m_mouseReleaseWasReceived(false)
521 bool DolphinTabPageSplitterHandle::event(QEvent
*event
)
523 switch (event
->type()) {
524 case QEvent::MouseButtonPress
:
525 m_mouseReleaseWasReceived
= false;
527 case QEvent::MouseButtonRelease
:
528 if (m_mouseReleaseWasReceived
) {
529 resetSplitterSizes();
531 m_mouseReleaseWasReceived
= !m_mouseReleaseWasReceived
;
533 case QEvent::MouseButtonDblClick
:
534 m_mouseReleaseWasReceived
= false;
535 resetSplitterSizes();
541 return QSplitterHandle::event(event
);
544 void DolphinTabPageSplitterHandle::resetSplitterSizes()
546 QList
<int> splitterSizes
= splitter()->sizes();
547 std::fill(splitterSizes
.begin(), splitterSizes
.end(), 0);
548 splitter()->setSizes(splitterSizes
);
551 DolphinTabPageSplitter::DolphinTabPageSplitter(Qt::Orientation orientation
, QWidget
*parent
)
552 : QSplitter(orientation
, parent
)
556 QSplitterHandle
*DolphinTabPageSplitter::createHandle()
558 return new DolphinTabPageSplitterHandle(orientation(), this);
561 #include "moc_dolphintabpage.cpp"