]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphintabpage.cpp
Merge branch 'release/20.12'
[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->show();
47
48 if (secondaryUrl.isValid() || GeneralSettings::splitView()) {
49 // Provide a secondary view, if the given secondary url is valid or if the
50 // startup settings are set this way (use the url of the primary view).
51 m_splitViewEnabled = true;
52 const QUrl& url = secondaryUrl.isValid() ? secondaryUrl : primaryUrl;
53 m_secondaryViewContainer = createViewContainer(url);
54 m_splitter->addWidget(m_secondaryViewContainer);
55 m_secondaryViewContainer->show();
56 }
57
58 m_primaryViewContainer->setActive(true);
59 }
60
61 bool DolphinTabPage::primaryViewActive() const
62 {
63 return m_primaryViewActive;
64 }
65
66 bool DolphinTabPage::splitViewEnabled() const
67 {
68 return m_splitViewEnabled;
69 }
70
71 void DolphinTabPage::setSplitViewEnabled(bool enabled, Animated animated, const QUrl &secondaryUrl)
72 {
73 if (m_splitViewEnabled != enabled) {
74 m_splitViewEnabled = enabled;
75 if (animated == WithAnimation && (
76 style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) < 1 ||
77 GlobalConfig::animationDurationFactor() <= 0.0)) {
78 animated = WithoutAnimation;
79 }
80 if (m_expandViewAnimation) {
81 m_expandViewAnimation->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped.
82 if (animated == WithoutAnimation) {
83 slotAnimationFinished();
84 }
85 }
86
87 if (enabled) {
88 QList<int> splitterSizes = m_splitter->sizes();
89 const QUrl& url = (secondaryUrl.isEmpty()) ? m_primaryViewContainer->url() : secondaryUrl;
90 m_secondaryViewContainer = createViewContainer(url);
91
92 auto secondaryNavigator = m_navigatorsWidget->secondaryUrlNavigator();
93 if (!secondaryNavigator) {
94 m_navigatorsWidget->createSecondaryUrlNavigator();
95 secondaryNavigator = m_navigatorsWidget->secondaryUrlNavigator();
96 }
97 m_secondaryViewContainer->connectUrlNavigator(secondaryNavigator);
98 m_navigatorsWidget->setSecondaryNavigatorVisible(true);
99 m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer,
100 m_secondaryViewContainer);
101
102 m_splitter->addWidget(m_secondaryViewContainer);
103 m_secondaryViewContainer->setActive(true);
104
105 if (animated == WithAnimation) {
106 m_secondaryViewContainer->setMinimumWidth(1);
107 splitterSizes.append(1);
108 m_splitter->setSizes(splitterSizes);
109 startExpandViewAnimation(m_secondaryViewContainer);
110 }
111 m_secondaryViewContainer->show();
112 } else {
113 m_navigatorsWidget->setSecondaryNavigatorVisible(false);
114 m_secondaryViewContainer->disconnectUrlNavigator();
115
116 DolphinViewContainer* view;
117 if (GeneralSettings::closeActiveSplitView()) {
118 view = activeViewContainer();
119 if (m_primaryViewActive) {
120 m_primaryViewContainer->disconnectUrlNavigator();
121 m_secondaryViewContainer->connectUrlNavigator(
122 m_navigatorsWidget->primaryUrlNavigator());
123
124 // If the primary view is active, we have to swap the pointers
125 // because the secondary view will be the new primary view.
126 qSwap(m_primaryViewContainer, m_secondaryViewContainer);
127 m_primaryViewActive = false;
128 }
129 } else {
130 view = m_primaryViewActive ? m_secondaryViewContainer : m_primaryViewContainer;
131 if (!m_primaryViewActive) {
132 m_primaryViewContainer->disconnectUrlNavigator();
133 m_secondaryViewContainer->connectUrlNavigator(
134 m_navigatorsWidget->primaryUrlNavigator());
135
136 // If the secondary view is active, we have to swap the pointers
137 // because the secondary view will be the new primary view.
138 qSwap(m_primaryViewContainer, m_secondaryViewContainer);
139 m_primaryViewActive = true;
140 }
141 }
142 m_primaryViewContainer->setActive(true);
143 m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer, nullptr);
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 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::restoreStateV1(const QByteArray& state)
332 {
333 if (state.isEmpty()) {
334 return;
335 }
336
337 QByteArray sd = state;
338 QDataStream stream(&sd, QIODevice::ReadOnly);
339
340 bool isSplitViewEnabled = false;
341 stream >> isSplitViewEnabled;
342 setSplitViewEnabled(isSplitViewEnabled, WithoutAnimation);
343
344 QUrl primaryUrl;
345 stream >> primaryUrl;
346 m_primaryViewContainer->setUrl(primaryUrl);
347 bool primaryUrlEditable;
348 stream >> primaryUrlEditable;
349 m_primaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable);
350
351 if (isSplitViewEnabled) {
352 QUrl secondaryUrl;
353 stream >> secondaryUrl;
354 m_secondaryViewContainer->setUrl(secondaryUrl);
355 bool secondaryUrlEditable;
356 stream >> secondaryUrlEditable;
357 m_secondaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable);
358 }
359
360 stream >> m_primaryViewActive;
361 if (m_primaryViewActive) {
362 m_primaryViewContainer->setActive(true);
363 } else {
364 Q_ASSERT(m_splitViewEnabled);
365 m_secondaryViewContainer->setActive(true);
366 }
367
368 QByteArray splitterState;
369 stream >> splitterState;
370 m_splitter->restoreState(splitterState);
371 }
372
373 void DolphinTabPage::setActive(bool active)
374 {
375 if (active) {
376 m_active = active;
377 } else {
378 // we should bypass changing active view in split mode
379 m_active = !m_splitViewEnabled;
380 }
381 // we want view to fire activated when goes from false to true
382 activeViewContainer()->setActive(active);
383 }
384
385 void DolphinTabPage::slotAnimationFinished()
386 {
387 for (int i = 0; i < m_splitter->count(); ++i) {
388 QWidget *viewContainer = m_splitter->widget(i);
389 if (viewContainer != m_primaryViewContainer &&
390 viewContainer != m_secondaryViewContainer) {
391 viewContainer->close();
392 viewContainer->deleteLater();
393 }
394 }
395 for (int i = 0; i < m_splitter->count(); ++i) {
396 QWidget *viewContainer = m_splitter->widget(i);
397 viewContainer->setMinimumWidth(viewContainer->minimumSizeHint().width());
398 }
399 m_expandingContainer = nullptr;
400 }
401
402 void DolphinTabPage::slotAnimationValueChanged(const QVariant& value)
403 {
404 Q_CHECK_PTR(m_expandingContainer);
405 const int indexOfExpandingContainer = m_splitter->indexOf(m_expandingContainer);
406 int indexOfNonExpandingContainer = -1;
407 if (m_expandingContainer == m_primaryViewContainer) {
408 indexOfNonExpandingContainer = m_splitter->indexOf(m_secondaryViewContainer);
409 } else {
410 indexOfNonExpandingContainer = m_splitter->indexOf(m_primaryViewContainer);
411 }
412 std::vector<QWidget *> widgetsToRemove;
413 const QList<int> oldSplitterSizes = m_splitter->sizes();
414 QList<int> newSplitterSizes{oldSplitterSizes};
415 int expansionWidthNeeded = value.toInt() - oldSplitterSizes.at(indexOfExpandingContainer);
416
417 // Reduce the size of the other widgets to make space for the expandingContainer.
418 for (int i = m_splitter->count() - 1; i >= 0; --i) {
419 if (m_splitter->widget(i) == m_primaryViewContainer ||
420 m_splitter->widget(i) == m_secondaryViewContainer) {
421 continue;
422 }
423 newSplitterSizes[i] = oldSplitterSizes.at(i) - expansionWidthNeeded;
424 expansionWidthNeeded = 0;
425 if (indexOfNonExpandingContainer != -1) {
426 // Make sure every zombie container is at least slightly reduced in size
427 // so it doesn't seem like they are here to stay.
428 newSplitterSizes[i]--;
429 newSplitterSizes[indexOfNonExpandingContainer]++;
430 }
431 if (newSplitterSizes.at(i) <= 0) {
432 expansionWidthNeeded -= newSplitterSizes.at(i);
433 newSplitterSizes[i] = 0;
434 widgetsToRemove.emplace_back(m_splitter->widget(i));
435 }
436 }
437 if (expansionWidthNeeded > 1 && indexOfNonExpandingContainer != -1) {
438 Q_ASSERT(m_splitViewEnabled);
439 newSplitterSizes[indexOfNonExpandingContainer] -= expansionWidthNeeded;
440 }
441 newSplitterSizes[indexOfExpandingContainer] = value.toInt();
442 m_splitter->setSizes(newSplitterSizes);
443 while (!widgetsToRemove.empty()) {
444 widgetsToRemove.back()->close();
445 widgetsToRemove.back()->deleteLater();
446 widgetsToRemove.pop_back();
447 }
448 }
449
450
451 void DolphinTabPage::slotViewActivated()
452 {
453 const DolphinView* oldActiveView = activeViewContainer()->view();
454
455 // Set the view, which was active before, to inactive
456 // and update the active view type, if tab is active
457 if (m_active) {
458 if (m_splitViewEnabled) {
459 activeViewContainer()->setActive(false);
460 m_primaryViewActive = !m_primaryViewActive;
461 } else {
462 m_primaryViewActive = true;
463 if (m_secondaryViewContainer) {
464 m_secondaryViewContainer->setActive(false);
465 }
466 }
467 }
468
469 const DolphinView* newActiveView = activeViewContainer()->view();
470
471 if (newActiveView == oldActiveView) {
472 return;
473 }
474
475 disconnect(oldActiveView, &DolphinView::urlChanged,
476 this, &DolphinTabPage::activeViewUrlChanged);
477 disconnect(oldActiveView, &DolphinView::redirection,
478 this, &DolphinTabPage::slotViewUrlRedirection);
479 connect(newActiveView, &DolphinView::urlChanged,
480 this, &DolphinTabPage::activeViewUrlChanged);
481 connect(newActiveView, &DolphinView::redirection,
482 this, &DolphinTabPage::slotViewUrlRedirection);
483 Q_EMIT activeViewChanged(activeViewContainer());
484 Q_EMIT activeViewUrlChanged(activeViewContainer()->url());
485 }
486
487 void DolphinTabPage::slotViewUrlRedirection(const QUrl& oldUrl, const QUrl& newUrl)
488 {
489 Q_UNUSED(oldUrl)
490
491 Q_EMIT activeViewUrlChanged(newUrl);
492 }
493
494 void DolphinTabPage::switchActiveView()
495 {
496 if (!m_splitViewEnabled) {
497 return;
498 }
499 if (m_primaryViewActive) {
500 m_secondaryViewContainer->setActive(true);
501 } else {
502 m_primaryViewContainer->setActive(true);
503 }
504 }
505
506 DolphinViewContainer* DolphinTabPage::createViewContainer(const QUrl& url) const
507 {
508 DolphinViewContainer* container = new DolphinViewContainer(url, m_splitter);
509 container->setActive(false);
510
511 const DolphinView* view = container->view();
512 connect(view, &DolphinView::activated,
513 this, &DolphinTabPage::slotViewActivated);
514
515 connect(view, &DolphinView::toggleActiveViewRequested,
516 this, &DolphinTabPage::switchActiveView);
517
518 return container;
519 }
520
521 void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer *expandingContainer)
522 {
523 Q_CHECK_PTR(expandingContainer);
524 Q_ASSERT(expandingContainer == m_primaryViewContainer ||
525 expandingContainer == m_secondaryViewContainer);
526 m_expandingContainer = expandingContainer;
527
528 m_expandViewAnimation = new QVariantAnimation(m_splitter);
529 m_expandViewAnimation->setDuration(2 *
530 style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) *
531 GlobalConfig::animationDurationFactor());
532 for (int i = 0; i < m_splitter->count(); ++i) {
533 m_splitter->widget(i)->setMinimumWidth(1);
534 }
535 connect(m_expandViewAnimation, &QAbstractAnimation::finished,
536 this, &DolphinTabPage::slotAnimationFinished);
537 connect(m_expandViewAnimation, &QVariantAnimation::valueChanged,
538 this, &DolphinTabPage::slotAnimationValueChanged);
539
540 m_expandViewAnimation->setStartValue(expandingContainer->width());
541 if (m_splitViewEnabled) { // A new viewContainer is being opened.
542 m_expandViewAnimation->setEndValue(m_splitter->width() / 2);
543 m_expandViewAnimation->setEasingCurve(QEasingCurve::OutCubic);
544 } else { // A viewContainer is being closed.
545 m_expandViewAnimation->setEndValue(m_splitter->width());
546 m_expandViewAnimation->setEasingCurve(QEasingCurve::InCubic);
547 }
548 m_expandViewAnimation->start(QAbstractAnimation::DeleteWhenStopped);
549 }