]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphintabpage.cpp
GIT_SILENT Update Appstream for new release
[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::slotAnimationFinished()
346 {
347 for (int i = 0; i < m_splitter->count(); ++i) {
348 QWidget *viewContainer = m_splitter->widget(i);
349 if (viewContainer != m_primaryViewContainer && viewContainer != m_secondaryViewContainer) {
350 viewContainer->close();
351 viewContainer->deleteLater();
352 }
353 }
354 for (int i = 0; i < m_splitter->count(); ++i) {
355 QWidget *viewContainer = m_splitter->widget(i);
356 viewContainer->setMinimumWidth(viewContainer->minimumSizeHint().width());
357 }
358 m_expandingContainer = nullptr;
359 }
360
361 void DolphinTabPage::slotAnimationValueChanged(const QVariant &value)
362 {
363 Q_CHECK_PTR(m_expandingContainer);
364 const int indexOfExpandingContainer = m_splitter->indexOf(m_expandingContainer);
365 int indexOfNonExpandingContainer = -1;
366 if (m_expandingContainer == m_primaryViewContainer) {
367 indexOfNonExpandingContainer = m_splitter->indexOf(m_secondaryViewContainer);
368 } else {
369 indexOfNonExpandingContainer = m_splitter->indexOf(m_primaryViewContainer);
370 }
371 std::vector<QWidget *> widgetsToRemove;
372 const QList<int> oldSplitterSizes = m_splitter->sizes();
373 QList<int> newSplitterSizes{oldSplitterSizes};
374 int expansionWidthNeeded = value.toInt() - oldSplitterSizes.at(indexOfExpandingContainer);
375
376 // Reduce the size of the other widgets to make space for the expandingContainer.
377 for (int i = m_splitter->count() - 1; i >= 0; --i) {
378 if (m_splitter->widget(i) == m_primaryViewContainer || 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 void DolphinTabPage::slotViewActivated()
409 {
410 const DolphinView *oldActiveView = activeViewContainer()->view();
411
412 // Set the view, which was active before, to inactive
413 // and update the active view type, if tab is active
414 if (m_active) {
415 if (m_splitViewEnabled) {
416 activeViewContainer()->setActive(false);
417 m_primaryViewActive = !m_primaryViewActive;
418 } else {
419 m_primaryViewActive = true;
420 if (m_secondaryViewContainer) {
421 m_secondaryViewContainer->setActive(false);
422 }
423 }
424 }
425
426 const DolphinView *newActiveView = activeViewContainer()->view();
427
428 if (newActiveView == oldActiveView) {
429 return;
430 }
431
432 disconnect(oldActiveView, &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
433 connect(newActiveView, &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
434 Q_EMIT activeViewChanged(activeViewContainer());
435 Q_EMIT activeViewUrlChanged(activeViewContainer()->url());
436 }
437
438 void DolphinTabPage::slotViewUrlRedirection(const QUrl &oldUrl, const QUrl &newUrl)
439 {
440 // Make sure the url of the view is updated. BUG:496414
441 if (splitViewEnabled()) {
442 if (primaryViewContainer()->view()->url() == oldUrl) {
443 primaryViewContainer()->view()->setUrl(newUrl);
444 }
445 if (secondaryViewContainer()->view()->url() == oldUrl) {
446 secondaryViewContainer()->view()->setUrl(newUrl);
447 }
448 } else {
449 activeViewContainer()->view()->setUrl(newUrl);
450 }
451 Q_EMIT activeViewUrlChanged(newUrl);
452 }
453
454 void DolphinTabPage::switchActiveView()
455 {
456 if (!m_splitViewEnabled) {
457 return;
458 }
459 if (m_primaryViewActive) {
460 m_secondaryViewContainer->setActive(true);
461 } else {
462 m_primaryViewContainer->setActive(true);
463 }
464 }
465
466 DolphinViewContainer *DolphinTabPage::createViewContainer(const QUrl &url) const
467 {
468 DolphinViewContainer *container = new DolphinViewContainer(url, m_splitter);
469 container->setActive(false);
470
471 const DolphinView *view = container->view();
472 connect(view, &DolphinView::activated, this, &DolphinTabPage::slotViewActivated);
473
474 connect(view, &DolphinView::toggleActiveViewRequested, 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 || expandingContainer == m_secondaryViewContainer);
483 m_expandingContainer = expandingContainer;
484
485 m_expandViewAnimation = new QVariantAnimation(m_splitter);
486 m_expandViewAnimation->setDuration(2 * style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) * GlobalConfig::animationDurationFactor());
487 for (int i = 0; i < m_splitter->count(); ++i) {
488 m_splitter->widget(i)->setMinimumWidth(1);
489 }
490 connect(m_expandViewAnimation, &QAbstractAnimation::finished, this, &DolphinTabPage::slotAnimationFinished);
491 connect(m_expandViewAnimation, &QVariantAnimation::valueChanged, this, &DolphinTabPage::slotAnimationValueChanged);
492
493 m_expandViewAnimation->setStartValue(expandingContainer->width());
494 if (m_splitViewEnabled) { // A new viewContainer is being opened.
495 m_expandViewAnimation->setEndValue(m_splitter->width() / 2);
496 m_expandViewAnimation->setEasingCurve(QEasingCurve::OutCubic);
497 } else { // A viewContainer is being closed.
498 m_expandViewAnimation->setEndValue(m_splitter->width());
499 m_expandViewAnimation->setEasingCurve(QEasingCurve::InCubic);
500 }
501 m_expandViewAnimation->start(QAbstractAnimation::DeleteWhenStopped);
502 }
503
504 DolphinTabPageSplitterHandle::DolphinTabPageSplitterHandle(Qt::Orientation orientation, QSplitter *parent)
505 : QSplitterHandle(orientation, parent)
506 , m_mouseReleaseWasReceived(false)
507 {
508 }
509
510 bool DolphinTabPageSplitterHandle::event(QEvent *event)
511 {
512 switch (event->type()) {
513 case QEvent::MouseButtonPress:
514 m_mouseReleaseWasReceived = false;
515 break;
516 case QEvent::MouseButtonRelease:
517 if (m_mouseReleaseWasReceived) {
518 resetSplitterSizes();
519 }
520 m_mouseReleaseWasReceived = !m_mouseReleaseWasReceived;
521 break;
522 case QEvent::MouseButtonDblClick:
523 m_mouseReleaseWasReceived = false;
524 resetSplitterSizes();
525 break;
526 default:
527 break;
528 }
529
530 return QSplitterHandle::event(event);
531 }
532
533 void DolphinTabPageSplitterHandle::resetSplitterSizes()
534 {
535 QList<int> splitterSizes = splitter()->sizes();
536 std::fill(splitterSizes.begin(), splitterSizes.end(), 0);
537 splitter()->setSizes(splitterSizes);
538 }
539
540 DolphinTabPageSplitter::DolphinTabPageSplitter(Qt::Orientation orientation, QWidget *parent)
541 : QSplitter(orientation, parent)
542 {
543 }
544
545 QSplitterHandle *DolphinTabPageSplitter::createHandle()
546 {
547 return new DolphinTabPageSplitterHandle(orientation(), this);
548 }
549
550 #include "moc_dolphintabpage.cpp"