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