]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphintabpage.cpp
Animate split view mode toggling
[dolphin.git] / src / dolphintabpage.cpp
1 /*
2 * SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
3 * SPDX-FileCopyrightText: 2020 Felix Ernst <fe.a.ernst@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "dolphintabpage.h"
9
10 #include "dolphin_generalsettings.h"
11 #include "dolphinviewcontainer.h"
12 #include "global.h"
13
14 #include <QVariantAnimation>
15 #include <QSplitter>
16 #include <QGridLayout>
17 #include <QWidgetAction>
18 #include <QStyle>
19
20 DolphinTabPage::DolphinTabPage(const QUrl &primaryUrl, const QUrl &secondaryUrl, QWidget* parent) :
21 QWidget(parent),
22 m_expandingContainer{nullptr},
23 m_primaryViewActive(true),
24 m_splitViewEnabled(false),
25 m_active(true)
26 {
27 QGridLayout *layout = new QGridLayout(this);
28 layout->setSpacing(0);
29 layout->setContentsMargins(0, 0, 0, 0);
30
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);
37
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);
44
45 m_splitter->addWidget(m_primaryViewContainer);
46 m_primaryViewContainer->installEventFilter(this);
47 m_primaryViewContainer->show();
48
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();
58 }
59
60 m_primaryViewContainer->setActive(true);
61 }
62
63 bool DolphinTabPage::primaryViewActive() const
64 {
65 return m_primaryViewActive;
66 }
67
68 bool DolphinTabPage::splitViewEnabled() const
69 {
70 return m_splitViewEnabled;
71 }
72
73 void DolphinTabPage::setSplitViewEnabled(bool enabled, Animated animated, const QUrl &secondaryUrl)
74 {
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;
81 }
82 if (m_expandViewAnimation) {
83 m_expandViewAnimation->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped.
84 if (animated == WithoutAnimation) {
85 slotAnimationFinished();
86 }
87 }
88
89 if (enabled) {
90 QList<int> splitterSizes = m_splitter->sizes();
91 const QUrl& url = (secondaryUrl.isEmpty()) ? m_primaryViewContainer->url() : secondaryUrl;
92 m_secondaryViewContainer = createViewContainer(url);
93
94 auto secondaryNavigator = m_navigatorsWidget->secondaryUrlNavigator();
95 if (!secondaryNavigator) {
96 m_navigatorsWidget->createSecondaryUrlNavigator();
97 secondaryNavigator = m_navigatorsWidget->secondaryUrlNavigator();
98 }
99 m_secondaryViewContainer->connectUrlNavigator(secondaryNavigator);
100 m_navigatorsWidget->setSecondaryNavigatorVisible(true);
101
102 m_splitter->addWidget(m_secondaryViewContainer);
103 m_secondaryViewContainer->installEventFilter(this);
104 m_secondaryViewContainer->setActive(true);
105
106 if (animated == WithAnimation) {
107 m_secondaryViewContainer->setMinimumWidth(1);
108 splitterSizes.append(1);
109 m_splitter->setSizes(splitterSizes);
110 startExpandViewAnimation(m_secondaryViewContainer);
111 }
112 m_secondaryViewContainer->show();
113 } else {
114 m_navigatorsWidget->setSecondaryNavigatorVisible(false);
115 m_secondaryViewContainer->disconnectUrlNavigator();
116
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());
124
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;
129 }
130 } else {
131 view = m_primaryViewActive ? m_secondaryViewContainer : m_primaryViewContainer;
132 if (!m_primaryViewActive) {
133 m_primaryViewContainer->disconnectUrlNavigator();
134 m_secondaryViewContainer->connectUrlNavigator(
135 m_navigatorsWidget->primaryUrlNavigator());
136
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;
141 }
142 }
143 m_primaryViewContainer->setActive(true);
144
145 if (animated == WithoutAnimation) {
146 view->close();
147 view->deleteLater();
148 } else {
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);
155 }
156 }
157 }
158 }
159
160 DolphinViewContainer* DolphinTabPage::primaryViewContainer() const
161 {
162 return m_primaryViewContainer;
163 }
164
165 DolphinViewContainer* DolphinTabPage::secondaryViewContainer() const
166 {
167 return m_secondaryViewContainer;
168 }
169
170 DolphinViewContainer* DolphinTabPage::activeViewContainer() const
171 {
172 return m_primaryViewActive ? m_primaryViewContainer :
173 m_secondaryViewContainer;
174 }
175
176 KFileItemList DolphinTabPage::selectedItems() const
177 {
178 KFileItemList items = m_primaryViewContainer->view()->selectedItems();
179 if (m_splitViewEnabled) {
180 items += m_secondaryViewContainer->view()->selectedItems();
181 }
182 return items;
183 }
184
185 int DolphinTabPage::selectedItemsCount() const
186 {
187 int selectedItemsCount = m_primaryViewContainer->view()->selectedItemsCount();
188 if (m_splitViewEnabled) {
189 selectedItemsCount += m_secondaryViewContainer->view()->selectedItemsCount();
190 }
191 return selectedItemsCount;
192 }
193
194 void DolphinTabPage::connectNavigators(DolphinNavigatorsWidgetAction *navigatorsWidget)
195 {
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);
203 }
204 resizeNavigators();
205 }
206
207 void DolphinTabPage::disconnectNavigators()
208 {
209 m_navigatorsWidget = nullptr;
210 m_primaryViewContainer->disconnectUrlNavigator();
211 if (m_splitViewEnabled) {
212 m_secondaryViewContainer->disconnectUrlNavigator();
213 }
214 }
215
216 bool DolphinTabPage::eventFilter(QObject *watched, QEvent *event)
217 {
218 if (event->type() == QEvent::Resize && m_navigatorsWidget) {
219 resizeNavigators();
220 return false;
221 }
222 return QWidget::eventFilter(watched, event);
223 }
224
225 void DolphinTabPage::insertNavigatorsWidget(DolphinNavigatorsWidgetAction* navigatorsWidget)
226 {
227 QGridLayout *gridLayout = static_cast<QGridLayout *>(layout());
228 if (navigatorsWidget->isInToolbar()) {
229 gridLayout->setRowMinimumHeight(0, 0);
230 } else {
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);
235 }
236 }
237
238
239 void DolphinTabPage::resizeNavigators() const
240 {
241 if (!m_secondaryViewContainer) {
242 m_navigatorsWidget->followViewContainerGeometry(
243 m_primaryViewContainer->mapToGlobal(QPoint(0,0)).x(),
244 m_primaryViewContainer->width());
245 } else {
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());
251 }
252 }
253
254 void DolphinTabPage::markUrlsAsSelected(const QList<QUrl>& urls)
255 {
256 m_primaryViewContainer->view()->markUrlsAsSelected(urls);
257 if (m_splitViewEnabled) {
258 m_secondaryViewContainer->view()->markUrlsAsSelected(urls);
259 }
260 }
261
262 void DolphinTabPage::markUrlAsCurrent(const QUrl& url)
263 {
264 m_primaryViewContainer->view()->markUrlAsCurrent(url);
265 if (m_splitViewEnabled) {
266 m_secondaryViewContainer->view()->markUrlAsCurrent(url);
267 }
268 }
269
270 void DolphinTabPage::refreshViews()
271 {
272 m_primaryViewContainer->readSettings();
273 if (m_splitViewEnabled) {
274 m_secondaryViewContainer->readSettings();
275 }
276 }
277
278 QByteArray DolphinTabPage::saveState() const
279 {
280 QByteArray state;
281 QDataStream stream(&state, QIODevice::WriteOnly);
282
283 stream << quint32(2); // Tab state version
284
285 stream << m_splitViewEnabled;
286
287 stream << m_primaryViewContainer->url();
288 stream << m_primaryViewContainer->urlNavigatorInternalWithHistory()->isUrlEditable();
289 m_primaryViewContainer->view()->saveState(stream);
290
291 if (m_splitViewEnabled) {
292 stream << m_secondaryViewContainer->url();
293 stream << m_secondaryViewContainer->urlNavigatorInternalWithHistory()->isUrlEditable();
294 m_secondaryViewContainer->view()->saveState(stream);
295 }
296
297 stream << m_primaryViewActive;
298 stream << m_splitter->saveState();
299
300 return state;
301 }
302
303 void DolphinTabPage::restoreState(const QByteArray& state)
304 {
305 if (state.isEmpty()) {
306 return;
307 }
308
309 QByteArray sd = state;
310 QDataStream stream(&sd, QIODevice::ReadOnly);
311
312 // Read the version number of the tab state and check if the version is supported.
313 quint32 version = 0;
314 stream >> version;
315 if (version != 2) {
316 // The version of the tab state isn't supported, we can't restore it.
317 return;
318 }
319
320 bool isSplitViewEnabled = false;
321 stream >> isSplitViewEnabled;
322 setSplitViewEnabled(isSplitViewEnabled, WithoutAnimation);
323
324 QUrl primaryUrl;
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);
331
332 if (isSplitViewEnabled) {
333 QUrl secondaryUrl;
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);
340 }
341
342 stream >> m_primaryViewActive;
343 if (m_primaryViewActive) {
344 m_primaryViewContainer->setActive(true);
345 } else {
346 Q_ASSERT(m_splitViewEnabled);
347 m_secondaryViewContainer->setActive(true);
348 }
349
350 QByteArray splitterState;
351 stream >> splitterState;
352 m_splitter->restoreState(splitterState);
353 }
354
355 void DolphinTabPage::restoreStateV1(const QByteArray& state)
356 {
357 if (state.isEmpty()) {
358 return;
359 }
360
361 QByteArray sd = state;
362 QDataStream stream(&sd, QIODevice::ReadOnly);
363
364 bool isSplitViewEnabled = false;
365 stream >> isSplitViewEnabled;
366 setSplitViewEnabled(isSplitViewEnabled, WithoutAnimation);
367
368 QUrl primaryUrl;
369 stream >> primaryUrl;
370 m_primaryViewContainer->setUrl(primaryUrl);
371 bool primaryUrlEditable;
372 stream >> primaryUrlEditable;
373 m_primaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable);
374
375 if (isSplitViewEnabled) {
376 QUrl secondaryUrl;
377 stream >> secondaryUrl;
378 m_secondaryViewContainer->setUrl(secondaryUrl);
379 bool secondaryUrlEditable;
380 stream >> secondaryUrlEditable;
381 m_secondaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable);
382 }
383
384 stream >> m_primaryViewActive;
385 if (m_primaryViewActive) {
386 m_primaryViewContainer->setActive(true);
387 } else {
388 Q_ASSERT(m_splitViewEnabled);
389 m_secondaryViewContainer->setActive(true);
390 }
391
392 QByteArray splitterState;
393 stream >> splitterState;
394 m_splitter->restoreState(splitterState);
395 }
396
397 void DolphinTabPage::setActive(bool active)
398 {
399 if (active) {
400 m_active = active;
401 } else {
402 // we should bypass changing active view in split mode
403 m_active = !m_splitViewEnabled;
404 }
405 // we want view to fire activated when goes from false to true
406 activeViewContainer()->setActive(active);
407 }
408
409 void DolphinTabPage::slotAnimationFinished()
410 {
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();
417 }
418 }
419 for (int i = 0; i < m_splitter->count(); ++i) {
420 QWidget *viewContainer = m_splitter->widget(i);
421 viewContainer->setMinimumWidth(viewContainer->minimumSizeHint().width());
422 }
423 m_expandingContainer = nullptr;
424 }
425
426 void DolphinTabPage::slotAnimationValueChanged(const QVariant& value)
427 {
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);
433 } else {
434 indexOfNonExpandingContainer = m_splitter->indexOf(m_primaryViewContainer);
435 }
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);
440
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) {
445 continue;
446 }
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]++;
454 }
455 if (newSplitterSizes.at(i) <= 0) {
456 expansionWidthNeeded -= newSplitterSizes.at(i);
457 newSplitterSizes[i] = 0;
458 widgetsToRemove.emplace_back(m_splitter->widget(i));
459 }
460 }
461 if (expansionWidthNeeded > 1 && indexOfNonExpandingContainer != -1) {
462 Q_ASSERT(m_splitViewEnabled);
463 newSplitterSizes[indexOfNonExpandingContainer] -= expansionWidthNeeded;
464 }
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();
471 }
472 }
473
474
475 void DolphinTabPage::slotViewActivated()
476 {
477 const DolphinView* oldActiveView = activeViewContainer()->view();
478
479 // Set the view, which was active before, to inactive
480 // and update the active view type, if tab is active
481 if (m_active) {
482 if (m_splitViewEnabled) {
483 activeViewContainer()->setActive(false);
484 m_primaryViewActive = !m_primaryViewActive;
485 } else {
486 m_primaryViewActive = true;
487 if (m_secondaryViewContainer) {
488 m_secondaryViewContainer->setActive(false);
489 }
490 }
491 }
492
493 const DolphinView* newActiveView = activeViewContainer()->view();
494
495 if (newActiveView == oldActiveView) {
496 return;
497 }
498
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());
509 }
510
511 void DolphinTabPage::slotViewUrlRedirection(const QUrl& oldUrl, const QUrl& newUrl)
512 {
513 Q_UNUSED(oldUrl)
514
515 Q_EMIT activeViewUrlChanged(newUrl);
516 }
517
518 void DolphinTabPage::switchActiveView()
519 {
520 if (!m_splitViewEnabled) {
521 return;
522 }
523 if (m_primaryViewActive) {
524 m_secondaryViewContainer->setActive(true);
525 } else {
526 m_primaryViewContainer->setActive(true);
527 }
528 }
529
530 DolphinViewContainer* DolphinTabPage::createViewContainer(const QUrl& url) const
531 {
532 DolphinViewContainer* container = new DolphinViewContainer(url, m_splitter);
533 container->setActive(false);
534
535 const DolphinView* view = container->view();
536 connect(view, &DolphinView::activated,
537 this, &DolphinTabPage::slotViewActivated);
538
539 connect(view, &DolphinView::toggleActiveViewRequested,
540 this, &DolphinTabPage::switchActiveView);
541
542 return container;
543 }
544
545 void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer *expandingContainer)
546 {
547 Q_CHECK_PTR(expandingContainer);
548 Q_ASSERT(expandingContainer == m_primaryViewContainer ||
549 expandingContainer == m_secondaryViewContainer);
550 m_expandingContainer = expandingContainer;
551
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);
558 }
559 connect(m_expandViewAnimation, &QAbstractAnimation::finished,
560 this, &DolphinTabPage::slotAnimationFinished);
561 connect(m_expandViewAnimation, &QVariantAnimation::valueChanged,
562 this, &DolphinTabPage::slotAnimationValueChanged);
563
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);
571 }
572 m_expandViewAnimation->start(QAbstractAnimation::DeleteWhenStopped);
573 }