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