]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphintabpage.cpp
9fde4071a383388d14ae4799a79bb268c2657e9e
[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
13 #include <QVariantAnimation>
14 #include <QGridLayout>
15 #include <QWidgetAction>
16 #include <QStyle>
17
18 DolphinTabPage::DolphinTabPage(const QUrl &primaryUrl, const QUrl &secondaryUrl, QWidget* parent) :
19 QWidget(parent),
20 m_expandingContainer{nullptr},
21 m_primaryViewActive(true),
22 m_splitViewEnabled(false),
23 m_active(true)
24 {
25 QGridLayout *layout = new QGridLayout(this);
26 layout->setSpacing(0);
27 layout->setContentsMargins(0, 0, 0, 0);
28
29 m_splitter = new DolphinTabPageSplitter(Qt::Horizontal, this);
30 m_splitter->setChildrenCollapsible(false);
31 connect(m_splitter, &QSplitter::splitterMoved,
32 this, &DolphinTabPage::splitterMoved);
33 layout->addWidget(m_splitter, 1, 0);
34 layout->setRowStretch(1, 1);
35
36 // Create a new primary view
37 m_primaryViewContainer = createViewContainer(primaryUrl);
38 connect(m_primaryViewContainer->view(), &DolphinView::urlChanged,
39 this, &DolphinTabPage::activeViewUrlChanged);
40 connect(m_primaryViewContainer->view(), &DolphinView::redirection,
41 this, &DolphinTabPage::slotViewUrlRedirection);
42
43 m_splitter->addWidget(m_primaryViewContainer);
44 m_primaryViewContainer->show();
45
46 if (secondaryUrl.isValid() || GeneralSettings::splitView()) {
47 // Provide a secondary view, if the given secondary url is valid or if the
48 // startup settings are set this way (use the url of the primary view).
49 m_splitViewEnabled = true;
50 const QUrl& url = secondaryUrl.isValid() ? secondaryUrl : primaryUrl;
51 m_secondaryViewContainer = createViewContainer(url);
52 m_splitter->addWidget(m_secondaryViewContainer);
53 m_secondaryViewContainer->show();
54 }
55
56 m_primaryViewContainer->setActive(true);
57 }
58
59 bool DolphinTabPage::primaryViewActive() const
60 {
61 return m_primaryViewActive;
62 }
63
64 bool DolphinTabPage::splitViewEnabled() const
65 {
66 return m_splitViewEnabled;
67 }
68
69 void DolphinTabPage::setSplitViewEnabled(bool enabled, Animated animated, const QUrl &secondaryUrl)
70 {
71 if (m_splitViewEnabled != enabled) {
72 m_splitViewEnabled = enabled;
73 if (animated == WithAnimation && (
74 style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) < 1 ||
75 GlobalConfig::animationDurationFactor() <= 0.0)) {
76 animated = WithoutAnimation;
77 }
78 if (m_expandViewAnimation) {
79 m_expandViewAnimation->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped.
80 if (animated == WithoutAnimation) {
81 slotAnimationFinished();
82 }
83 }
84
85 if (enabled) {
86 QList<int> splitterSizes = m_splitter->sizes();
87 const QUrl& url = (secondaryUrl.isEmpty()) ? m_primaryViewContainer->url() : secondaryUrl;
88 m_secondaryViewContainer = createViewContainer(url);
89
90 auto secondaryNavigator = m_navigatorsWidget->secondaryUrlNavigator();
91 if (!secondaryNavigator) {
92 m_navigatorsWidget->createSecondaryUrlNavigator();
93 secondaryNavigator = m_navigatorsWidget->secondaryUrlNavigator();
94 }
95 m_secondaryViewContainer->connectUrlNavigator(secondaryNavigator);
96 m_navigatorsWidget->setSecondaryNavigatorVisible(true);
97 m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer,
98 m_secondaryViewContainer);
99
100 m_splitter->addWidget(m_secondaryViewContainer);
101 m_secondaryViewContainer->setActive(true);
102
103 if (animated == WithAnimation) {
104 m_secondaryViewContainer->setMinimumWidth(1);
105 splitterSizes.append(1);
106 m_splitter->setSizes(splitterSizes);
107 startExpandViewAnimation(m_secondaryViewContainer);
108 }
109 m_secondaryViewContainer->show();
110 } else {
111 m_navigatorsWidget->setSecondaryNavigatorVisible(false);
112 m_secondaryViewContainer->disconnectUrlNavigator();
113
114 DolphinViewContainer* view;
115 if (GeneralSettings::closeActiveSplitView()) {
116 view = activeViewContainer();
117 if (m_primaryViewActive) {
118 m_primaryViewContainer->disconnectUrlNavigator();
119 m_secondaryViewContainer->connectUrlNavigator(
120 m_navigatorsWidget->primaryUrlNavigator());
121
122 // If the primary view is active, we have to swap the pointers
123 // because the secondary view will be the new primary view.
124 qSwap(m_primaryViewContainer, m_secondaryViewContainer);
125 m_primaryViewActive = false;
126 }
127 } else {
128 view = m_primaryViewActive ? m_secondaryViewContainer : m_primaryViewContainer;
129 if (!m_primaryViewActive) {
130 m_primaryViewContainer->disconnectUrlNavigator();
131 m_secondaryViewContainer->connectUrlNavigator(
132 m_navigatorsWidget->primaryUrlNavigator());
133
134 // If the secondary view is active, we have to swap the pointers
135 // because the secondary view will be the new primary view.
136 qSwap(m_primaryViewContainer, m_secondaryViewContainer);
137 m_primaryViewActive = true;
138 }
139 }
140 m_primaryViewContainer->setActive(true);
141 m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer, nullptr);
142
143 if (animated == WithoutAnimation) {
144 view->close();
145 view->deleteLater();
146 } else {
147 // Kill it but keep it as a zombie for the closing animation.
148 m_secondaryViewContainer = nullptr;
149 view->blockSignals(true);
150 view->view()->blockSignals(true);
151 view->setDisabled(true);
152 startExpandViewAnimation(m_primaryViewContainer);
153 }
154
155 m_primaryViewContainer->slotSplitTabDisabled();
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 m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer,
205 m_secondaryViewContainer);
206 }
207
208 void DolphinTabPage::disconnectNavigators()
209 {
210 m_navigatorsWidget = nullptr;
211 m_primaryViewContainer->disconnectUrlNavigator();
212 if (m_splitViewEnabled) {
213 m_secondaryViewContainer->disconnectUrlNavigator();
214 }
215 }
216
217 void DolphinTabPage::insertNavigatorsWidget(DolphinNavigatorsWidgetAction* navigatorsWidget)
218 {
219 QGridLayout *gridLayout = static_cast<QGridLayout *>(layout());
220 if (navigatorsWidget->isInToolbar()) {
221 gridLayout->setRowMinimumHeight(0, 0);
222 } else {
223 // We set a row minimum height, so the height does not visibly change whenever
224 // navigatorsWidget is inserted which happens every time the current tab is changed.
225 gridLayout->setRowMinimumHeight(0, navigatorsWidget->primaryUrlNavigator()->height());
226 gridLayout->addWidget(navigatorsWidget->requestWidget(this), 0, 0);
227 }
228 }
229
230 void DolphinTabPage::markUrlsAsSelected(const QList<QUrl>& urls)
231 {
232 m_primaryViewContainer->view()->markUrlsAsSelected(urls);
233 if (m_splitViewEnabled) {
234 m_secondaryViewContainer->view()->markUrlsAsSelected(urls);
235 }
236 }
237
238 void DolphinTabPage::markUrlAsCurrent(const QUrl& url)
239 {
240 m_primaryViewContainer->view()->markUrlAsCurrent(url);
241 if (m_splitViewEnabled) {
242 m_secondaryViewContainer->view()->markUrlAsCurrent(url);
243 }
244 }
245
246 void DolphinTabPage::refreshViews()
247 {
248 m_primaryViewContainer->readSettings();
249 if (m_splitViewEnabled) {
250 m_secondaryViewContainer->readSettings();
251 }
252 }
253
254 QByteArray DolphinTabPage::saveState() const
255 {
256 QByteArray state;
257 QDataStream stream(&state, QIODevice::WriteOnly);
258
259 stream << quint32(2); // Tab state version
260
261 stream << m_splitViewEnabled;
262
263 stream << m_primaryViewContainer->url();
264 stream << m_primaryViewContainer->urlNavigatorInternalWithHistory()->isUrlEditable();
265 m_primaryViewContainer->view()->saveState(stream);
266
267 if (m_splitViewEnabled) {
268 stream << m_secondaryViewContainer->url();
269 stream << m_secondaryViewContainer->urlNavigatorInternalWithHistory()->isUrlEditable();
270 m_secondaryViewContainer->view()->saveState(stream);
271 }
272
273 stream << m_primaryViewActive;
274 stream << m_splitter->saveState();
275
276 return state;
277 }
278
279 void DolphinTabPage::restoreState(const QByteArray& state)
280 {
281 if (state.isEmpty()) {
282 return;
283 }
284
285 QByteArray sd = state;
286 QDataStream stream(&sd, QIODevice::ReadOnly);
287
288 // Read the version number of the tab state and check if the version is supported.
289 quint32 version = 0;
290 stream >> version;
291 if (version != 2) {
292 // The version of the tab state isn't supported, we can't restore it.
293 return;
294 }
295
296 bool isSplitViewEnabled = false;
297 stream >> isSplitViewEnabled;
298 setSplitViewEnabled(isSplitViewEnabled, WithoutAnimation);
299
300 QUrl primaryUrl;
301 stream >> primaryUrl;
302 m_primaryViewContainer->setUrl(primaryUrl);
303 bool primaryUrlEditable;
304 stream >> primaryUrlEditable;
305 m_primaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable);
306 m_primaryViewContainer->view()->restoreState(stream);
307
308 if (isSplitViewEnabled) {
309 QUrl secondaryUrl;
310 stream >> secondaryUrl;
311 m_secondaryViewContainer->setUrl(secondaryUrl);
312 bool secondaryUrlEditable;
313 stream >> secondaryUrlEditable;
314 m_secondaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable);
315 m_secondaryViewContainer->view()->restoreState(stream);
316 }
317
318 stream >> m_primaryViewActive;
319 if (m_primaryViewActive) {
320 m_primaryViewContainer->setActive(true);
321 } else {
322 Q_ASSERT(m_splitViewEnabled);
323 m_secondaryViewContainer->setActive(true);
324 }
325
326 QByteArray splitterState;
327 stream >> splitterState;
328 m_splitter->restoreState(splitterState);
329 }
330
331 void DolphinTabPage::setActive(bool active)
332 {
333 if (active) {
334 m_active = active;
335 } else {
336 // we should bypass changing active view in split mode
337 m_active = !m_splitViewEnabled;
338 }
339 // we want view to fire activated when goes from false to true
340 activeViewContainer()->setActive(active);
341 }
342
343 void DolphinTabPage::slotAnimationFinished()
344 {
345 for (int i = 0; i < m_splitter->count(); ++i) {
346 QWidget *viewContainer = m_splitter->widget(i);
347 if (viewContainer != m_primaryViewContainer &&
348 viewContainer != m_secondaryViewContainer) {
349 viewContainer->close();
350 viewContainer->deleteLater();
351 }
352 }
353 for (int i = 0; i < m_splitter->count(); ++i) {
354 QWidget *viewContainer = m_splitter->widget(i);
355 viewContainer->setMinimumWidth(viewContainer->minimumSizeHint().width());
356 }
357 m_expandingContainer = nullptr;
358 }
359
360 void DolphinTabPage::slotAnimationValueChanged(const QVariant& value)
361 {
362 Q_CHECK_PTR(m_expandingContainer);
363 const int indexOfExpandingContainer = m_splitter->indexOf(m_expandingContainer);
364 int indexOfNonExpandingContainer = -1;
365 if (m_expandingContainer == m_primaryViewContainer) {
366 indexOfNonExpandingContainer = m_splitter->indexOf(m_secondaryViewContainer);
367 } else {
368 indexOfNonExpandingContainer = m_splitter->indexOf(m_primaryViewContainer);
369 }
370 std::vector<QWidget *> widgetsToRemove;
371 const QList<int> oldSplitterSizes = m_splitter->sizes();
372 QList<int> newSplitterSizes{oldSplitterSizes};
373 int expansionWidthNeeded = value.toInt() - oldSplitterSizes.at(indexOfExpandingContainer);
374
375 // Reduce the size of the other widgets to make space for the expandingContainer.
376 for (int i = m_splitter->count() - 1; i >= 0; --i) {
377 if (m_splitter->widget(i) == m_primaryViewContainer ||
378 m_splitter->widget(i) == m_secondaryViewContainer) {
379 continue;
380 }
381 newSplitterSizes[i] = oldSplitterSizes.at(i) - expansionWidthNeeded;
382 expansionWidthNeeded = 0;
383 if (indexOfNonExpandingContainer != -1) {
384 // Make sure every zombie container is at least slightly reduced in size
385 // so it doesn't seem like they are here to stay.
386 newSplitterSizes[i]--;
387 newSplitterSizes[indexOfNonExpandingContainer]++;
388 }
389 if (newSplitterSizes.at(i) <= 0) {
390 expansionWidthNeeded -= newSplitterSizes.at(i);
391 newSplitterSizes[i] = 0;
392 widgetsToRemove.emplace_back(m_splitter->widget(i));
393 }
394 }
395 if (expansionWidthNeeded > 1 && indexOfNonExpandingContainer != -1) {
396 Q_ASSERT(m_splitViewEnabled);
397 newSplitterSizes[indexOfNonExpandingContainer] -= expansionWidthNeeded;
398 }
399 newSplitterSizes[indexOfExpandingContainer] = value.toInt();
400 m_splitter->setSizes(newSplitterSizes);
401 while (!widgetsToRemove.empty()) {
402 widgetsToRemove.back()->close();
403 widgetsToRemove.back()->deleteLater();
404 widgetsToRemove.pop_back();
405 }
406 }
407
408
409 void DolphinTabPage::slotViewActivated()
410 {
411 const DolphinView* oldActiveView = activeViewContainer()->view();
412
413 // Set the view, which was active before, to inactive
414 // and update the active view type, if tab is active
415 if (m_active) {
416 if (m_splitViewEnabled) {
417 activeViewContainer()->setActive(false);
418 m_primaryViewActive = !m_primaryViewActive;
419 } else {
420 m_primaryViewActive = true;
421 if (m_secondaryViewContainer) {
422 m_secondaryViewContainer->setActive(false);
423 }
424 }
425 }
426
427 const DolphinView* newActiveView = activeViewContainer()->view();
428
429 if (newActiveView == oldActiveView) {
430 return;
431 }
432
433 disconnect(oldActiveView, &DolphinView::urlChanged,
434 this, &DolphinTabPage::activeViewUrlChanged);
435 disconnect(oldActiveView, &DolphinView::redirection,
436 this, &DolphinTabPage::slotViewUrlRedirection);
437 connect(newActiveView, &DolphinView::urlChanged,
438 this, &DolphinTabPage::activeViewUrlChanged);
439 connect(newActiveView, &DolphinView::redirection,
440 this, &DolphinTabPage::slotViewUrlRedirection);
441 Q_EMIT activeViewChanged(activeViewContainer());
442 Q_EMIT activeViewUrlChanged(activeViewContainer()->url());
443 }
444
445 void DolphinTabPage::slotViewUrlRedirection(const QUrl& oldUrl, const QUrl& newUrl)
446 {
447 Q_UNUSED(oldUrl)
448
449 Q_EMIT activeViewUrlChanged(newUrl);
450 }
451
452 void DolphinTabPage::switchActiveView()
453 {
454 if (!m_splitViewEnabled) {
455 return;
456 }
457 if (m_primaryViewActive) {
458 m_secondaryViewContainer->setActive(true);
459 } else {
460 m_primaryViewContainer->setActive(true);
461 }
462 }
463
464 DolphinViewContainer* DolphinTabPage::createViewContainer(const QUrl& url) const
465 {
466 DolphinViewContainer* container = new DolphinViewContainer(url, m_splitter);
467 container->setActive(false);
468
469 const DolphinView* view = container->view();
470 connect(view, &DolphinView::activated,
471 this, &DolphinTabPage::slotViewActivated);
472
473 connect(view, &DolphinView::toggleActiveViewRequested,
474 this, &DolphinTabPage::switchActiveView);
475
476 return container;
477 }
478
479 void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer *expandingContainer)
480 {
481 Q_CHECK_PTR(expandingContainer);
482 Q_ASSERT(expandingContainer == m_primaryViewContainer ||
483 expandingContainer == m_secondaryViewContainer);
484 m_expandingContainer = expandingContainer;
485
486 m_expandViewAnimation = new QVariantAnimation(m_splitter);
487 m_expandViewAnimation->setDuration(2 *
488 style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) *
489 GlobalConfig::animationDurationFactor());
490 for (int i = 0; i < m_splitter->count(); ++i) {
491 m_splitter->widget(i)->setMinimumWidth(1);
492 }
493 connect(m_expandViewAnimation, &QAbstractAnimation::finished,
494 this, &DolphinTabPage::slotAnimationFinished);
495 connect(m_expandViewAnimation, &QVariantAnimation::valueChanged,
496 this, &DolphinTabPage::slotAnimationValueChanged);
497
498 m_expandViewAnimation->setStartValue(expandingContainer->width());
499 if (m_splitViewEnabled) { // A new viewContainer is being opened.
500 m_expandViewAnimation->setEndValue(m_splitter->width() / 2);
501 m_expandViewAnimation->setEasingCurve(QEasingCurve::OutCubic);
502 } else { // A viewContainer is being closed.
503 m_expandViewAnimation->setEndValue(m_splitter->width());
504 m_expandViewAnimation->setEasingCurve(QEasingCurve::InCubic);
505 }
506 m_expandViewAnimation->start(QAbstractAnimation::DeleteWhenStopped);
507 }
508
509 DolphinTabPageSplitterHandle::DolphinTabPageSplitterHandle(Qt::Orientation orientation, QSplitter *parent)
510 : QSplitterHandle(orientation, parent)
511 , m_mouseReleaseWasReceived(false)
512 {}
513
514 bool DolphinTabPageSplitterHandle::event(QEvent *event)
515 {
516 switch (event->type()) {
517 case QEvent::MouseButtonPress:
518 m_mouseReleaseWasReceived = false;
519 break;
520 case QEvent::MouseButtonRelease:
521 if (m_mouseReleaseWasReceived) {
522 resetSplitterSizes();
523 }
524 m_mouseReleaseWasReceived = !m_mouseReleaseWasReceived;
525 break;
526 case QEvent::MouseButtonDblClick:
527 m_mouseReleaseWasReceived = false;
528 resetSplitterSizes();
529 break;
530 default:
531 break;
532 }
533
534 return QSplitterHandle::event(event);
535 }
536
537 void DolphinTabPageSplitterHandle::resetSplitterSizes()
538 {
539 QList<int> splitterSizes = splitter()->sizes();
540 std::fill(splitterSizes.begin(), splitterSizes.end(), 0);
541 splitter()->setSizes(splitterSizes);
542 }
543
544 DolphinTabPageSplitter::DolphinTabPageSplitter(Qt::Orientation orientation, QWidget *parent)
545 : QSplitter(orientation, parent)
546 {}
547
548 QSplitterHandle* DolphinTabPageSplitter::createHandle()
549 {
550 return new DolphinTabPageSplitterHandle(orientation(), this);
551 }