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