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