]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphintabpage.cpp
Rewrite search integration
[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 if (!m_customLabel.isEmpty()) {
279 stream << m_customLabel;
280 }
281
282 return state;
283 }
284
285 void DolphinTabPage::restoreState(const QByteArray &state)
286 {
287 if (state.isEmpty()) {
288 return;
289 }
290
291 QByteArray sd = state;
292 QDataStream stream(&sd, QIODevice::ReadOnly);
293
294 // Read the version number of the tab state and check if the version is supported.
295 quint32 version = 0;
296 stream >> version;
297 if (version != 2) {
298 // The version of the tab state isn't supported, we can't restore it.
299 return;
300 }
301
302 bool isSplitViewEnabled = false;
303 stream >> isSplitViewEnabled;
304 setSplitViewEnabled(isSplitViewEnabled, WithoutAnimation);
305
306 QUrl primaryUrl;
307 stream >> primaryUrl;
308 m_primaryViewContainer->setUrl(primaryUrl);
309 bool primaryUrlEditable;
310 stream >> primaryUrlEditable;
311 m_primaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(primaryUrlEditable);
312 m_primaryViewContainer->view()->restoreState(stream);
313
314 if (isSplitViewEnabled) {
315 QUrl secondaryUrl;
316 stream >> secondaryUrl;
317 m_secondaryViewContainer->setUrl(secondaryUrl);
318 bool secondaryUrlEditable;
319 stream >> secondaryUrlEditable;
320 m_secondaryViewContainer->urlNavigatorInternalWithHistory()->setUrlEditable(secondaryUrlEditable);
321 m_secondaryViewContainer->view()->restoreState(stream);
322 }
323
324 stream >> m_primaryViewActive;
325 if (m_primaryViewActive) {
326 m_primaryViewContainer->setActive(true);
327 } else {
328 Q_ASSERT(m_splitViewEnabled);
329 m_secondaryViewContainer->setActive(true);
330 }
331
332 QByteArray splitterState;
333 stream >> splitterState;
334 m_splitter->restoreState(splitterState);
335
336 if (!stream.atEnd()) {
337 QString tabTitle;
338 stream >> tabTitle;
339 setCustomLabel(tabTitle);
340 }
341 }
342
343 void DolphinTabPage::setActive(bool active)
344 {
345 if (active) {
346 m_active = active;
347 } else {
348 // we should bypass changing active view in split mode
349 m_active = !m_splitViewEnabled;
350 }
351 // we want view to fire activated when goes from false to true
352 activeViewContainer()->setActive(active);
353 }
354
355 void DolphinTabPage::setCustomLabel(const QString &label)
356 {
357 m_customLabel = label;
358 }
359
360 QString DolphinTabPage::customLabel() const
361 {
362 return m_customLabel;
363 }
364
365 void DolphinTabPage::slotAnimationFinished()
366 {
367 for (int i = 0; i < m_splitter->count(); ++i) {
368 QWidget *viewContainer = m_splitter->widget(i);
369 if (viewContainer != m_primaryViewContainer && viewContainer != m_secondaryViewContainer) {
370 viewContainer->close();
371 viewContainer->deleteLater();
372 }
373 }
374 for (int i = 0; i < m_splitter->count(); ++i) {
375 QWidget *viewContainer = m_splitter->widget(i);
376 viewContainer->setMinimumWidth(viewContainer->minimumSizeHint().width());
377 }
378 m_expandingContainer = nullptr;
379 }
380
381 void DolphinTabPage::slotAnimationValueChanged(const QVariant &value)
382 {
383 Q_CHECK_PTR(m_expandingContainer);
384 const int indexOfExpandingContainer = m_splitter->indexOf(m_expandingContainer);
385 int indexOfNonExpandingContainer = -1;
386 if (m_expandingContainer == m_primaryViewContainer) {
387 indexOfNonExpandingContainer = m_splitter->indexOf(m_secondaryViewContainer);
388 } else {
389 indexOfNonExpandingContainer = m_splitter->indexOf(m_primaryViewContainer);
390 }
391 std::vector<QWidget *> widgetsToRemove;
392 const QList<int> oldSplitterSizes = m_splitter->sizes();
393 QList<int> newSplitterSizes{oldSplitterSizes};
394 int expansionWidthNeeded = value.toInt() - oldSplitterSizes.at(indexOfExpandingContainer);
395
396 // Reduce the size of the other widgets to make space for the expandingContainer.
397 for (int i = m_splitter->count() - 1; i >= 0; --i) {
398 if (m_splitter->widget(i) == m_primaryViewContainer || m_splitter->widget(i) == m_secondaryViewContainer) {
399 continue;
400 }
401 newSplitterSizes[i] = oldSplitterSizes.at(i) - expansionWidthNeeded;
402 expansionWidthNeeded = 0;
403 if (indexOfNonExpandingContainer != -1) {
404 // Make sure every zombie container is at least slightly reduced in size
405 // so it doesn't seem like they are here to stay.
406 newSplitterSizes[i]--;
407 newSplitterSizes[indexOfNonExpandingContainer]++;
408 }
409 if (newSplitterSizes.at(i) <= 0) {
410 expansionWidthNeeded -= newSplitterSizes.at(i);
411 newSplitterSizes[i] = 0;
412 widgetsToRemove.emplace_back(m_splitter->widget(i));
413 }
414 }
415 if (expansionWidthNeeded > 1 && indexOfNonExpandingContainer != -1) {
416 Q_ASSERT(m_splitViewEnabled);
417 newSplitterSizes[indexOfNonExpandingContainer] -= expansionWidthNeeded;
418 }
419 newSplitterSizes[indexOfExpandingContainer] = value.toInt();
420 m_splitter->setSizes(newSplitterSizes);
421 while (!widgetsToRemove.empty()) {
422 widgetsToRemove.back()->close();
423 widgetsToRemove.back()->deleteLater();
424 widgetsToRemove.pop_back();
425 }
426 }
427
428 void DolphinTabPage::slotViewActivated()
429 {
430 const DolphinView *oldActiveView = activeViewContainer()->view();
431
432 // Set the view, which was active before, to inactive
433 // and update the active view type, if tab is active
434 if (m_active) {
435 if (m_splitViewEnabled) {
436 activeViewContainer()->setActive(false);
437 m_primaryViewActive = !m_primaryViewActive;
438 } else {
439 m_primaryViewActive = true;
440 if (m_secondaryViewContainer) {
441 m_secondaryViewContainer->setActive(false);
442 }
443 }
444 }
445
446 const DolphinView *newActiveView = activeViewContainer()->view();
447
448 if (newActiveView == oldActiveView) {
449 return;
450 }
451
452 disconnect(oldActiveView, &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
453 connect(newActiveView, &DolphinView::urlChanged, this, &DolphinTabPage::activeViewUrlChanged);
454 Q_EMIT activeViewChanged(activeViewContainer());
455 Q_EMIT activeViewUrlChanged(activeViewContainer()->url());
456 }
457
458 void DolphinTabPage::slotViewUrlRedirection(const QUrl &oldUrl, const QUrl &newUrl)
459 {
460 // Make sure the url of the view is updated. BUG:496414
461 if (splitViewEnabled()) {
462 if (primaryViewContainer()->view()->url() == oldUrl) {
463 primaryViewContainer()->view()->setUrl(newUrl);
464 }
465 if (secondaryViewContainer()->view()->url() == oldUrl) {
466 secondaryViewContainer()->view()->setUrl(newUrl);
467 }
468 } else {
469 activeViewContainer()->view()->setUrl(newUrl);
470 }
471 Q_EMIT activeViewUrlChanged(newUrl);
472 }
473
474 void DolphinTabPage::switchActiveView()
475 {
476 if (!m_splitViewEnabled) {
477 return;
478 }
479 if (m_primaryViewActive) {
480 m_secondaryViewContainer->setActive(true);
481 } else {
482 m_primaryViewContainer->setActive(true);
483 }
484 }
485
486 DolphinViewContainer *DolphinTabPage::createViewContainer(const QUrl &url) const
487 {
488 DolphinViewContainer *container = new DolphinViewContainer(url, m_splitter);
489 container->setActive(false);
490
491 const DolphinView *view = container->view();
492 connect(view, &DolphinView::activated, this, &DolphinTabPage::slotViewActivated);
493
494 connect(view, &DolphinView::toggleActiveViewRequested, this, &DolphinTabPage::switchActiveView);
495
496 return container;
497 }
498
499 void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer *expandingContainer)
500 {
501 Q_CHECK_PTR(expandingContainer);
502 Q_ASSERT(expandingContainer == m_primaryViewContainer || expandingContainer == m_secondaryViewContainer);
503 m_expandingContainer = expandingContainer;
504
505 m_expandViewAnimation = new QVariantAnimation(m_splitter);
506 m_expandViewAnimation->setDuration(2 * style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) * GlobalConfig::animationDurationFactor());
507 for (int i = 0; i < m_splitter->count(); ++i) {
508 m_splitter->widget(i)->setMinimumWidth(1);
509 }
510 connect(m_expandViewAnimation, &QAbstractAnimation::finished, this, &DolphinTabPage::slotAnimationFinished);
511 connect(m_expandViewAnimation, &QVariantAnimation::valueChanged, this, &DolphinTabPage::slotAnimationValueChanged);
512
513 m_expandViewAnimation->setStartValue(expandingContainer->width());
514 if (m_splitViewEnabled) { // A new viewContainer is being opened.
515 m_expandViewAnimation->setEndValue(m_splitter->width() / 2);
516 m_expandViewAnimation->setEasingCurve(QEasingCurve::OutCubic);
517 } else { // A viewContainer is being closed.
518 m_expandViewAnimation->setEndValue(m_splitter->width());
519 m_expandViewAnimation->setEasingCurve(QEasingCurve::InCubic);
520 }
521 m_expandViewAnimation->start(QAbstractAnimation::DeleteWhenStopped);
522 }
523
524 DolphinTabPageSplitterHandle::DolphinTabPageSplitterHandle(Qt::Orientation orientation, QSplitter *parent)
525 : QSplitterHandle(orientation, parent)
526 , m_mouseReleaseWasReceived(false)
527 {
528 }
529
530 bool DolphinTabPageSplitterHandle::event(QEvent *event)
531 {
532 switch (event->type()) {
533 case QEvent::MouseButtonPress:
534 m_mouseReleaseWasReceived = false;
535 break;
536 case QEvent::MouseButtonRelease:
537 if (m_mouseReleaseWasReceived) {
538 resetSplitterSizes();
539 }
540 m_mouseReleaseWasReceived = !m_mouseReleaseWasReceived;
541 break;
542 case QEvent::MouseButtonDblClick:
543 m_mouseReleaseWasReceived = false;
544 resetSplitterSizes();
545 break;
546 default:
547 break;
548 }
549
550 return QSplitterHandle::event(event);
551 }
552
553 void DolphinTabPageSplitterHandle::resetSplitterSizes()
554 {
555 QList<int> splitterSizes = splitter()->sizes();
556 std::fill(splitterSizes.begin(), splitterSizes.end(), 0);
557 splitter()->setSizes(splitterSizes);
558 }
559
560 DolphinTabPageSplitter::DolphinTabPageSplitter(Qt::Orientation orientation, QWidget *parent)
561 : QSplitter(orientation, parent)
562 {
563 }
564
565 QSplitterHandle *DolphinTabPageSplitter::createHandle()
566 {
567 return new DolphinTabPageSplitterHandle(orientation(), this);
568 }
569
570 #include "moc_dolphintabpage.cpp"