]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphintabpage.cpp
Add clang-format and format code as in Frameworks
[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 <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 KFileItemList DolphinTabPage::selectedItems() const
168 {
169 KFileItemList items = m_primaryViewContainer->view()->selectedItems();
170 if (m_splitViewEnabled) {
171 items += m_secondaryViewContainer->view()->selectedItems();
172 }
173 return items;
174 }
175
176 int DolphinTabPage::selectedItemsCount() const
177 {
178 int selectedItemsCount = m_primaryViewContainer->view()->selectedItemsCount();
179 if (m_splitViewEnabled) {
180 selectedItemsCount += m_secondaryViewContainer->view()->selectedItemsCount();
181 }
182 return selectedItemsCount;
183 }
184
185 void DolphinTabPage::connectNavigators(DolphinNavigatorsWidgetAction *navigatorsWidget)
186 {
187 insertNavigatorsWidget(navigatorsWidget);
188 m_navigatorsWidget = navigatorsWidget;
189 auto primaryNavigator = navigatorsWidget->primaryUrlNavigator();
190 m_primaryViewContainer->connectUrlNavigator(primaryNavigator);
191 if (m_splitViewEnabled) {
192 auto secondaryNavigator = navigatorsWidget->secondaryUrlNavigator();
193 m_secondaryViewContainer->connectUrlNavigator(secondaryNavigator);
194 }
195 m_navigatorsWidget->followViewContainersGeometry(m_primaryViewContainer, m_secondaryViewContainer);
196 }
197
198 void DolphinTabPage::disconnectNavigators()
199 {
200 m_navigatorsWidget = nullptr;
201 m_primaryViewContainer->disconnectUrlNavigator();
202 if (m_splitViewEnabled) {
203 m_secondaryViewContainer->disconnectUrlNavigator();
204 }
205 }
206
207 void DolphinTabPage::insertNavigatorsWidget(DolphinNavigatorsWidgetAction *navigatorsWidget)
208 {
209 QGridLayout *gridLayout = static_cast<QGridLayout *>(layout());
210 if (navigatorsWidget->isInToolbar()) {
211 gridLayout->setRowMinimumHeight(0, 0);
212 } else {
213 // We set a row minimum height, so the height does not visibly change whenever
214 // navigatorsWidget is inserted which happens every time the current tab is changed.
215 gridLayout->setRowMinimumHeight(0, navigatorsWidget->primaryUrlNavigator()->height());
216 gridLayout->addWidget(navigatorsWidget->requestWidget(this), 0, 0);
217 }
218 }
219
220 void DolphinTabPage::markUrlsAsSelected(const QList<QUrl> &urls)
221 {
222 m_primaryViewContainer->view()->markUrlsAsSelected(urls);
223 if (m_splitViewEnabled) {
224 m_secondaryViewContainer->view()->markUrlsAsSelected(urls);
225 }
226 }
227
228 void DolphinTabPage::markUrlAsCurrent(const QUrl &url)
229 {
230 m_primaryViewContainer->view()->markUrlAsCurrent(url);
231 if (m_splitViewEnabled) {
232 m_secondaryViewContainer->view()->markUrlAsCurrent(url);
233 }
234 }
235
236 void DolphinTabPage::refreshViews()
237 {
238 m_primaryViewContainer->readSettings();
239 if (m_splitViewEnabled) {
240 m_secondaryViewContainer->readSettings();
241 }
242 }
243
244 QByteArray DolphinTabPage::saveState() const
245 {
246 QByteArray state;
247 QDataStream stream(&state, QIODevice::WriteOnly);
248
249 stream << quint32(2); // Tab state version
250
251 stream << m_splitViewEnabled;
252
253 stream << m_primaryViewContainer->url();
254 stream << m_primaryViewContainer->urlNavigatorInternalWithHistory()->isUrlEditable();
255 m_primaryViewContainer->view()->saveState(stream);
256
257 if (m_splitViewEnabled) {
258 stream << m_secondaryViewContainer->url();
259 stream << m_secondaryViewContainer->urlNavigatorInternalWithHistory()->isUrlEditable();
260 m_secondaryViewContainer->view()->saveState(stream);
261 }
262
263 stream << m_primaryViewActive;
264 stream << m_splitter->saveState();
265
266 return state;
267 }
268
269 void DolphinTabPage::restoreState(const QByteArray &state)
270 {
271 if (state.isEmpty()) {
272 return;
273 }
274
275 QByteArray sd = state;
276 QDataStream stream(&sd, QIODevice::ReadOnly);
277
278 // Read the version number of the tab state and check if the version is supported.
279 quint32 version = 0;
280 stream >> version;
281 if (version != 2) {
282 // The version of the tab state isn't supported, we can't restore it.
283 return;
284 }
285
286 bool isSplitViewEnabled = false;
287 stream >> isSplitViewEnabled;
288 setSplitViewEnabled(isSplitViewEnabled, WithoutAnimation);
289
290 QUrl primaryUrl;
291 stream >> primaryUrl;
292 m_primaryViewContainer->setUrl(primaryUrl);
293 bool primaryUrlEditable;
294 stream >> primaryUrlEditable;
295 m_primaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable);
296 m_primaryViewContainer->view()->restoreState(stream);
297
298 if (isSplitViewEnabled) {
299 QUrl secondaryUrl;
300 stream >> secondaryUrl;
301 m_secondaryViewContainer->setUrl(secondaryUrl);
302 bool secondaryUrlEditable;
303 stream >> secondaryUrlEditable;
304 m_secondaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable);
305 m_secondaryViewContainer->view()->restoreState(stream);
306 }
307
308 stream >> m_primaryViewActive;
309 if (m_primaryViewActive) {
310 m_primaryViewContainer->setActive(true);
311 } else {
312 Q_ASSERT(m_splitViewEnabled);
313 m_secondaryViewContainer->setActive(true);
314 }
315
316 QByteArray splitterState;
317 stream >> splitterState;
318 m_splitter->restoreState(splitterState);
319 }
320
321 void DolphinTabPage::setActive(bool active)
322 {
323 if (active) {
324 m_active = active;
325 } else {
326 // we should bypass changing active view in split mode
327 m_active = !m_splitViewEnabled;
328 }
329 // we want view to fire activated when goes from false to true
330 activeViewContainer()->setActive(active);
331 }
332
333 void DolphinTabPage::slotAnimationFinished()
334 {
335 for (int i = 0; i < m_splitter->count(); ++i) {
336 QWidget *viewContainer = m_splitter->widget(i);
337 if (viewContainer != m_primaryViewContainer && viewContainer != m_secondaryViewContainer) {
338 viewContainer->close();
339 viewContainer->deleteLater();
340 }
341 }
342 for (int i = 0; i < m_splitter->count(); ++i) {
343 QWidget *viewContainer = m_splitter->widget(i);
344 viewContainer->setMinimumWidth(viewContainer->minimumSizeHint().width());
345 }
346 m_expandingContainer = nullptr;
347 }
348
349 void DolphinTabPage::slotAnimationValueChanged(const QVariant &value)
350 {
351 Q_CHECK_PTR(m_expandingContainer);
352 const int indexOfExpandingContainer = m_splitter->indexOf(m_expandingContainer);
353 int indexOfNonExpandingContainer = -1;
354 if (m_expandingContainer == m_primaryViewContainer) {
355 indexOfNonExpandingContainer = m_splitter->indexOf(m_secondaryViewContainer);
356 } else {
357 indexOfNonExpandingContainer = m_splitter->indexOf(m_primaryViewContainer);
358 }
359 std::vector<QWidget *> widgetsToRemove;
360 const QList<int> oldSplitterSizes = m_splitter->sizes();
361 QList<int> newSplitterSizes{oldSplitterSizes};
362 int expansionWidthNeeded = value.toInt() - oldSplitterSizes.at(indexOfExpandingContainer);
363
364 // Reduce the size of the other widgets to make space for the expandingContainer.
365 for (int i = m_splitter->count() - 1; i >= 0; --i) {
366 if (m_splitter->widget(i) == m_primaryViewContainer || m_splitter->widget(i) == m_secondaryViewContainer) {
367 continue;
368 }
369 newSplitterSizes[i] = oldSplitterSizes.at(i) - expansionWidthNeeded;
370 expansionWidthNeeded = 0;
371 if (indexOfNonExpandingContainer != -1) {
372 // Make sure every zombie container is at least slightly reduced in size
373 // so it doesn't seem like they are here to stay.
374 newSplitterSizes[i]--;
375 newSplitterSizes[indexOfNonExpandingContainer]++;
376 }
377 if (newSplitterSizes.at(i) <= 0) {
378 expansionWidthNeeded -= newSplitterSizes.at(i);
379 newSplitterSizes[i] = 0;
380 widgetsToRemove.emplace_back(m_splitter->widget(i));
381 }
382 }
383 if (expansionWidthNeeded > 1 && indexOfNonExpandingContainer != -1) {
384 Q_ASSERT(m_splitViewEnabled);
385 newSplitterSizes[indexOfNonExpandingContainer] -= expansionWidthNeeded;
386 }
387 newSplitterSizes[indexOfExpandingContainer] = value.toInt();
388 m_splitter->setSizes(newSplitterSizes);
389 while (!widgetsToRemove.empty()) {
390 widgetsToRemove.back()->close();
391 widgetsToRemove.back()->deleteLater();
392 widgetsToRemove.pop_back();
393 }
394 }
395
396 void DolphinTabPage::slotViewActivated()
397 {
398 const DolphinView *oldActiveView = activeViewContainer()->view();
399
400 // Set the view, which was active before, to inactive
401 // and update the active view type, if tab is active
402 if (m_active) {
403 if (m_splitViewEnabled) {
404 activeViewContainer()->setActive(false);
405 m_primaryViewActive = !m_primaryViewActive;
406 } else {
407 m_primaryViewActive = true;
408 if (m_secondaryViewContainer) {
409 m_secondaryViewContainer->setActive(false);
410 }
411 }
412 }
413
414 const DolphinView *newActiveView = activeViewContainer()->view();
415
416 if (newActiveView == oldActiveView) {
417 return;
418 }
419
420 disconnect(oldActiveView, &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
421 disconnect(oldActiveView, &DolphinView::redirection, this, &DolphinTabPage::slotViewUrlRedirection);
422 connect(newActiveView, &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
423 connect(newActiveView, &DolphinView::redirection, this, &DolphinTabPage::slotViewUrlRedirection);
424 Q_EMIT activeViewChanged(activeViewContainer());
425 Q_EMIT activeViewUrlChanged(activeViewContainer()->url());
426 }
427
428 void DolphinTabPage::slotViewUrlRedirection(const QUrl &oldUrl, const QUrl &newUrl)
429 {
430 Q_UNUSED(oldUrl)
431
432 Q_EMIT activeViewUrlChanged(newUrl);
433 }
434
435 void DolphinTabPage::switchActiveView()
436 {
437 if (!m_splitViewEnabled) {
438 return;
439 }
440 if (m_primaryViewActive) {
441 m_secondaryViewContainer->setActive(true);
442 } else {
443 m_primaryViewContainer->setActive(true);
444 }
445 }
446
447 DolphinViewContainer *DolphinTabPage::createViewContainer(const QUrl &url) const
448 {
449 DolphinViewContainer *container = new DolphinViewContainer(url, m_splitter);
450 container->setActive(false);
451
452 const DolphinView *view = container->view();
453 connect(view, &DolphinView::activated, this, &DolphinTabPage::slotViewActivated);
454
455 connect(view, &DolphinView::toggleActiveViewRequested, this, &DolphinTabPage::switchActiveView);
456
457 return container;
458 }
459
460 void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer *expandingContainer)
461 {
462 Q_CHECK_PTR(expandingContainer);
463 Q_ASSERT(expandingContainer == m_primaryViewContainer || expandingContainer == m_secondaryViewContainer);
464 m_expandingContainer = expandingContainer;
465
466 m_expandViewAnimation = new QVariantAnimation(m_splitter);
467 m_expandViewAnimation->setDuration(2 * style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) * GlobalConfig::animationDurationFactor());
468 for (int i = 0; i < m_splitter->count(); ++i) {
469 m_splitter->widget(i)->setMinimumWidth(1);
470 }
471 connect(m_expandViewAnimation, &QAbstractAnimation::finished, this, &DolphinTabPage::slotAnimationFinished);
472 connect(m_expandViewAnimation, &QVariantAnimation::valueChanged, this, &DolphinTabPage::slotAnimationValueChanged);
473
474 m_expandViewAnimation->setStartValue(expandingContainer->width());
475 if (m_splitViewEnabled) { // A new viewContainer is being opened.
476 m_expandViewAnimation->setEndValue(m_splitter->width() / 2);
477 m_expandViewAnimation->setEasingCurve(QEasingCurve::OutCubic);
478 } else { // A viewContainer is being closed.
479 m_expandViewAnimation->setEndValue(m_splitter->width());
480 m_expandViewAnimation->setEasingCurve(QEasingCurve::InCubic);
481 }
482 m_expandViewAnimation->start(QAbstractAnimation::DeleteWhenStopped);
483 }
484
485 DolphinTabPageSplitterHandle::DolphinTabPageSplitterHandle(Qt::Orientation orientation, QSplitter *parent)
486 : QSplitterHandle(orientation, parent)
487 , m_mouseReleaseWasReceived(false)
488 {
489 }
490
491 bool DolphinTabPageSplitterHandle::event(QEvent *event)
492 {
493 switch (event->type()) {
494 case QEvent::MouseButtonPress:
495 m_mouseReleaseWasReceived = false;
496 break;
497 case QEvent::MouseButtonRelease:
498 if (m_mouseReleaseWasReceived) {
499 resetSplitterSizes();
500 }
501 m_mouseReleaseWasReceived = !m_mouseReleaseWasReceived;
502 break;
503 case QEvent::MouseButtonDblClick:
504 m_mouseReleaseWasReceived = false;
505 resetSplitterSizes();
506 break;
507 default:
508 break;
509 }
510
511 return QSplitterHandle::event(event);
512 }
513
514 void DolphinTabPageSplitterHandle::resetSplitterSizes()
515 {
516 QList<int> splitterSizes = splitter()->sizes();
517 std::fill(splitterSizes.begin(), splitterSizes.end(), 0);
518 splitter()->setSizes(splitterSizes);
519 }
520
521 DolphinTabPageSplitter::DolphinTabPageSplitter(Qt::Orientation orientation, QWidget *parent)
522 : QSplitter(orientation, parent)
523 {
524 }
525
526 QSplitterHandle *DolphinTabPageSplitter::createHandle()
527 {
528 return new DolphinTabPageSplitterHandle(orientation(), this);
529 }