]> cloud.milkyroute.net Git - dolphin.git/blob - src/dolphintabwidget.cpp
GIT_SILENT Update Appstream for new release
[dolphin.git] / src / dolphintabwidget.cpp
1 /*
2 * SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "dolphintabwidget.h"
8
9 #include "dolphin_generalsettings.h"
10 #include "dolphintabbar.h"
11 #include "dolphinviewcontainer.h"
12 #include "views/draganddrophelper.h"
13
14 #include <KAcceleratorManager>
15 #include <KConfigGroup>
16 #include <KIO/CommandLauncherJob>
17 #include <KLocalizedString>
18 #include <KShell>
19 #include <KStringHandler>
20 #include <kio/global.h>
21
22 #include <QApplication>
23 #include <QDropEvent>
24 #include <QStackedWidget>
25
26 DolphinTabWidget::DolphinTabWidget(DolphinNavigatorsWidgetAction *navigatorsWidget, QWidget *parent)
27 : QTabWidget(parent)
28 , m_lastViewedTab(nullptr)
29 , m_navigatorsWidget{navigatorsWidget}
30 {
31 KAcceleratorManager::setNoAccel(this);
32
33 connect(this, &DolphinTabWidget::tabCloseRequested, this, QOverload<int>::of(&DolphinTabWidget::closeTab));
34 connect(this, &DolphinTabWidget::currentChanged, this, &DolphinTabWidget::currentTabChanged);
35
36 DolphinTabBar *tabBar = new DolphinTabBar(this);
37 connect(tabBar, &DolphinTabBar::openNewActivatedTab, this, QOverload<int>::of(&DolphinTabWidget::openNewActivatedTab));
38 connect(tabBar, &DolphinTabBar::tabDragMoveEvent, this, &DolphinTabWidget::tabDragMoveEvent);
39 connect(tabBar, &DolphinTabBar::tabDropEvent, this, &DolphinTabWidget::tabDropEvent);
40 connect(tabBar, &DolphinTabBar::tabDetachRequested, this, &DolphinTabWidget::detachTab);
41
42 setTabBar(tabBar);
43 setDocumentMode(true);
44 setElideMode(Qt::ElideRight);
45 setUsesScrollButtons(true);
46 setTabBarAutoHide(true);
47
48 auto stackWidget{findChild<QStackedWidget *>()};
49 // i18n: This accessible name will be announced any time the user moves keyboard focus e.g. from the toolbar or the places panel towards the main working
50 // area of Dolphin. It gives structure. This container does not only contain the main view but also the status bar, the search panel, filter, and selection
51 // mode bars, so calling it just a "View" is a bit wrong, but hopefully still gets the point across.
52 stackWidget->setAccessibleName(i18nc("accessible name of Dolphin's view container", "Location View")); // Without this call, the non-descript Qt provided
53 // "Layered Pane" role is announced.
54 }
55
56 DolphinTabPage *DolphinTabWidget::currentTabPage() const
57 {
58 return tabPageAt(currentIndex());
59 }
60
61 DolphinTabPage *DolphinTabWidget::nextTabPage() const
62 {
63 const int index = currentIndex() + 1;
64 return tabPageAt(index < count() ? index : 0);
65 }
66
67 DolphinTabPage *DolphinTabWidget::prevTabPage() const
68 {
69 const int index = currentIndex() - 1;
70 return tabPageAt(index >= 0 ? index : (count() - 1));
71 }
72
73 DolphinTabPage *DolphinTabWidget::tabPageAt(const int index) const
74 {
75 return static_cast<DolphinTabPage *>(widget(index));
76 }
77
78 void DolphinTabWidget::saveProperties(KConfigGroup &group) const
79 {
80 const int tabCount = count();
81 group.writeEntry("Tab Count", tabCount);
82 group.writeEntry("Active Tab Index", currentIndex());
83
84 for (int i = 0; i < tabCount; ++i) {
85 const DolphinTabPage *tabPage = tabPageAt(i);
86 group.writeEntry("Tab Data " % QString::number(i), tabPage->saveState());
87 }
88 }
89
90 void DolphinTabWidget::readProperties(const KConfigGroup &group)
91 {
92 const int tabCount = group.readEntry("Tab Count", 0);
93 for (int i = 0; i < tabCount; ++i) {
94 if (i >= count()) {
95 openNewActivatedTab();
96 }
97 const QByteArray state = group.readEntry("Tab Data " % QString::number(i), QByteArray());
98 tabPageAt(i)->restoreState(state);
99 }
100
101 const int index = group.readEntry("Active Tab Index", 0);
102 setCurrentIndex(index);
103 }
104
105 void DolphinTabWidget::refreshViews()
106 {
107 // Left-elision is better when showing full paths, since you care most
108 // about the current directory which is on the right
109 if (GeneralSettings::showFullPathInTitlebar()) {
110 setElideMode(Qt::ElideLeft);
111 } else {
112 setElideMode(Qt::ElideRight);
113 }
114
115 const int tabCount = count();
116 for (int i = 0; i < tabCount; ++i) {
117 updateTabName(i);
118 tabPageAt(i)->refreshViews();
119 }
120 }
121
122 void DolphinTabWidget::updateTabName(int index)
123 {
124 Q_ASSERT(index >= 0);
125 tabBar()->setTabText(index, tabName(tabPageAt(index)));
126 }
127
128 bool DolphinTabWidget::isUrlOpen(const QUrl &url) const
129 {
130 return viewOpenAtDirectory(url).has_value();
131 }
132
133 bool DolphinTabWidget::isItemVisibleInAnyView(const QUrl &urlOfItem) const
134 {
135 return viewShowingItem(urlOfItem).has_value();
136 }
137
138 void DolphinTabWidget::openNewActivatedTab()
139 {
140 std::unique_ptr<DolphinUrlNavigator::VisualState> oldNavigatorState;
141 if (currentTabPage()->primaryViewActive() || !m_navigatorsWidget->secondaryUrlNavigator()) {
142 oldNavigatorState = m_navigatorsWidget->primaryUrlNavigator()->visualState();
143 } else {
144 oldNavigatorState = m_navigatorsWidget->secondaryUrlNavigator()->visualState();
145 }
146
147 const DolphinViewContainer *oldActiveViewContainer = currentTabPage()->activeViewContainer();
148 Q_ASSERT(oldActiveViewContainer);
149
150 openNewActivatedTab(oldActiveViewContainer->url());
151
152 DolphinViewContainer *newActiveViewContainer = currentTabPage()->activeViewContainer();
153 Q_ASSERT(newActiveViewContainer);
154
155 // The URL navigator of the new tab should have the same editable state
156 // as the current tab
157 newActiveViewContainer->urlNavigator()->setVisualState(*oldNavigatorState.get());
158
159 // Always focus the new tab's view
160 newActiveViewContainer->view()->setFocus();
161 }
162
163 void DolphinTabWidget::openNewActivatedTab(const QUrl &primaryUrl, const QUrl &secondaryUrl)
164 {
165 openNewTab(primaryUrl, secondaryUrl);
166 if (GeneralSettings::openNewTabAfterLastTab()) {
167 setCurrentIndex(count() - 1);
168 } else {
169 setCurrentIndex(currentIndex() + 1);
170 }
171 }
172
173 DolphinTabPage *DolphinTabWidget::openNewTab(const QUrl &primaryUrl, const QUrl &secondaryUrl, DolphinTabWidget::NewTabPosition position)
174 {
175 QWidget *focusWidget = QApplication::focusWidget();
176
177 DolphinTabPage *tabPage = new DolphinTabPage(primaryUrl, secondaryUrl, this);
178 tabPage->setActive(false);
179 connect(tabPage, &DolphinTabPage::activeViewChanged, this, &DolphinTabWidget::activeViewChanged);
180 connect(tabPage, &DolphinTabPage::activeViewUrlChanged, this, &DolphinTabWidget::tabUrlChanged);
181 connect(tabPage->activeViewContainer(), &DolphinViewContainer::captionChanged, this, [this, tabPage]() {
182 updateTabName(indexOf(tabPage));
183 });
184
185 if (position == NewTabPosition::FollowSetting) {
186 if (GeneralSettings::openNewTabAfterLastTab()) {
187 position = NewTabPosition::AtEnd;
188 } else {
189 position = NewTabPosition::AfterCurrent;
190 }
191 }
192
193 int newTabIndex = -1;
194 if (position == NewTabPosition::AfterCurrent || (position == NewTabPosition::FollowSetting && !GeneralSettings::openNewTabAfterLastTab())) {
195 newTabIndex = currentIndex() + 1;
196 }
197
198 insertTab(newTabIndex, tabPage, QIcon() /* loaded in tabInserted */, tabName(tabPage));
199
200 if (focusWidget) {
201 // The DolphinViewContainer grabbed the keyboard focus. As the tab is opened
202 // in background, assure that the previous focused widget gets the focus back.
203 focusWidget->setFocus();
204 }
205 return tabPage;
206 }
207
208 void DolphinTabWidget::openDirectories(const QList<QUrl> &dirs, bool splitView)
209 {
210 Q_ASSERT(dirs.size() > 0);
211
212 bool somethingWasAlreadyOpen = false;
213
214 QList<QUrl>::const_iterator it = dirs.constBegin();
215 while (it != dirs.constEnd()) {
216 const QUrl &primaryUrl = *(it++);
217 const std::optional<ViewIndex> viewIndexAtDirectory = viewOpenAtDirectory(primaryUrl);
218
219 // When the user asks for a URL that's already open,
220 // activate it instead of opening a new tab
221 if (viewIndexAtDirectory.has_value()) {
222 somethingWasAlreadyOpen = true;
223 activateViewContainerAt(viewIndexAtDirectory.value());
224 } else if (splitView && (it != dirs.constEnd())) {
225 const QUrl &secondaryUrl = *(it++);
226 if (somethingWasAlreadyOpen) {
227 openNewTab(primaryUrl, secondaryUrl);
228 } else {
229 openNewActivatedTab(primaryUrl, secondaryUrl);
230 }
231 } else {
232 if (somethingWasAlreadyOpen) {
233 openNewTab(primaryUrl);
234 } else {
235 openNewActivatedTab(primaryUrl);
236 }
237 }
238 }
239 }
240
241 void DolphinTabWidget::openFiles(const QList<QUrl> &files, bool splitView)
242 {
243 Q_ASSERT(files.size() > 0);
244
245 // Get all distinct directories from 'files'.
246 QList<QUrl> dirsThatNeedToBeOpened;
247 QList<QUrl> dirsThatWereAlreadyOpen;
248 for (const QUrl &file : files) {
249 const QUrl dir(file.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
250 if (dirsThatNeedToBeOpened.contains(dir) || dirsThatWereAlreadyOpen.contains(dir)) {
251 continue;
252 }
253
254 // The selecting of files that we do later will not work in views that already have items selected.
255 // So we check if dir is already open and clear the selection if it is. BUG: 417230
256 // We also make sure the view will be activated.
257 auto viewIndex = viewShowingItem(file);
258 if (viewIndex.has_value()) {
259 viewContainerAt(viewIndex.value())->view()->clearSelection();
260 activateViewContainerAt(viewIndex.value());
261 dirsThatWereAlreadyOpen.append(dir);
262 } else {
263 dirsThatNeedToBeOpened.append(dir);
264 }
265 }
266
267 const int oldTabCount = count();
268 // Open a tab for each directory. If the "split view" option is enabled,
269 // two directories are shown inside one tab (see openDirectories()).
270 if (dirsThatNeedToBeOpened.size() > 0) {
271 openDirectories(dirsThatNeedToBeOpened, splitView);
272 }
273 const int tabCount = count();
274
275 // Select the files. Although the files can be split between several
276 // tabs, there is no need to split 'files' accordingly, as
277 // the DolphinView will just ignore invalid selections.
278 for (int i = 0; i < tabCount; ++i) {
279 DolphinTabPage *tabPage = tabPageAt(i);
280 tabPage->markUrlsAsSelected(files);
281 tabPage->markUrlAsCurrent(files.first());
282 if (i < oldTabCount) {
283 // Force selection of file if directory was already open, BUG: 417230
284 tabPage->activeViewContainer()->view()->updateViewState();
285 }
286 }
287 }
288
289 void DolphinTabWidget::closeTab()
290 {
291 closeTab(currentIndex());
292 }
293
294 void DolphinTabWidget::closeTab(const int index)
295 {
296 Q_ASSERT(index >= 0);
297 Q_ASSERT(index < count());
298
299 if (count() < 2) {
300 // Close Dolphin when closing the last tab.
301 parentWidget()->close();
302 return;
303 }
304
305 DolphinTabPage *tabPage = tabPageAt(index);
306 Q_EMIT rememberClosedTab(tabPage->activeViewContainer()->url(), tabPage->saveState());
307
308 removeTab(index);
309 tabPage->deleteLater();
310 }
311
312 void DolphinTabWidget::activateTab(const int index)
313 {
314 if (index < count()) {
315 setCurrentIndex(index);
316 }
317 }
318
319 void DolphinTabWidget::activateLastTab()
320 {
321 setCurrentIndex(count() - 1);
322 }
323
324 void DolphinTabWidget::activateNextTab()
325 {
326 const int index = currentIndex() + 1;
327 setCurrentIndex(index < count() ? index : 0);
328 }
329
330 void DolphinTabWidget::activatePrevTab()
331 {
332 const int index = currentIndex() - 1;
333 setCurrentIndex(index >= 0 ? index : (count() - 1));
334 }
335
336 void DolphinTabWidget::restoreClosedTab(const QByteArray &state)
337 {
338 openNewActivatedTab();
339 currentTabPage()->restoreState(state);
340 }
341
342 void DolphinTabWidget::copyToInactiveSplitView()
343 {
344 const DolphinTabPage *tabPage = currentTabPage();
345 if (!tabPage->splitViewEnabled()) {
346 return;
347 }
348
349 const KFileItemList selectedItems = tabPage->activeViewContainer()->view()->selectedItems();
350 if (selectedItems.isEmpty()) {
351 return;
352 }
353
354 DolphinView *const inactiveView = tabPage->inactiveViewContainer()->view();
355 inactiveView->copySelectedItems(selectedItems, inactiveView->url());
356 }
357
358 void DolphinTabWidget::moveToInactiveSplitView()
359 {
360 const DolphinTabPage *tabPage = currentTabPage();
361 if (!tabPage->splitViewEnabled()) {
362 return;
363 }
364
365 const KFileItemList selectedItems = tabPage->activeViewContainer()->view()->selectedItems();
366 if (selectedItems.isEmpty()) {
367 return;
368 }
369
370 DolphinView *const inactiveView = tabPage->inactiveViewContainer()->view();
371 inactiveView->moveSelectedItems(selectedItems, inactiveView->url());
372 }
373
374 void DolphinTabWidget::detachTab(int index)
375 {
376 Q_ASSERT(index >= 0);
377
378 QStringList args;
379
380 const DolphinTabPage *tabPage = tabPageAt(index);
381 args << tabPage->primaryViewContainer()->url().url();
382 if (tabPage->splitViewEnabled()) {
383 args << tabPage->secondaryViewContainer()->url().url();
384 args << QStringLiteral("--split");
385 }
386 args << QStringLiteral("--new-window");
387
388 KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob("dolphin", args, this);
389 job->setDesktopName(QStringLiteral("org.kde.dolphin"));
390 job->start();
391
392 closeTab(index);
393 }
394
395 void DolphinTabWidget::openNewActivatedTab(int index)
396 {
397 Q_ASSERT(index >= 0);
398 const DolphinTabPage *tabPage = tabPageAt(index);
399 openNewActivatedTab(tabPage->activeViewContainer()->url());
400 }
401
402 void DolphinTabWidget::tabDragMoveEvent(int index, QDragMoveEvent *event)
403 {
404 if (index >= 0) {
405 DolphinView *view = tabPageAt(index)->activeViewContainer()->view();
406 DragAndDropHelper::updateDropAction(event, view->url());
407 }
408 }
409
410 void DolphinTabWidget::tabDropEvent(int index, QDropEvent *event)
411 {
412 if (index >= 0) {
413 DolphinView *view = tabPageAt(index)->activeViewContainer()->view();
414 view->dropUrls(view->url(), event, view);
415 } else {
416 const auto urls = event->mimeData()->urls();
417
418 for (const QUrl &url : urls) {
419 auto *job = KIO::stat(url, KIO::StatJob::SourceSide, KIO::StatDetail::StatBasic, KIO::JobFlag::HideProgressInfo);
420 connect(job, &KJob::result, this, [this, job]() {
421 if (!job->error() && job->statResult().isDir()) {
422 openNewTab(job->url(), QUrl(), NewTabPosition::AtEnd);
423 }
424 });
425 }
426 }
427 }
428
429 void DolphinTabWidget::tabUrlChanged(const QUrl &url)
430 {
431 const int index = indexOf(qobject_cast<QWidget *>(sender()));
432 if (index >= 0) {
433 updateTabName(index);
434 tabBar()->setTabToolTip(index, url.toDisplayString(QUrl::PreferLocalFile));
435 if (tabBar()->isVisible()) {
436 // ensure the path url ends with a slash to have proper folder icon for remote folders
437 const QUrl pathUrl = QUrl(url.adjusted(QUrl::StripTrailingSlash).toString(QUrl::FullyEncoded).append("/"));
438 tabBar()->setTabIcon(index, QIcon::fromTheme(KIO::iconNameForUrl(pathUrl)));
439 } else {
440 // Mark as dirty, actually load once the tab bar actually gets shown
441 tabBar()->setTabIcon(index, QIcon());
442 }
443
444 // Emit the currentUrlChanged signal if the url of the current tab has been changed.
445 if (index == currentIndex()) {
446 Q_EMIT currentUrlChanged(url);
447 }
448
449 Q_EMIT urlChanged(url);
450 }
451 }
452
453 void DolphinTabWidget::currentTabChanged(int index)
454 {
455 DolphinTabPage *tabPage = tabPageAt(index);
456 if (tabPage == m_lastViewedTab) {
457 return;
458 }
459 if (m_lastViewedTab) {
460 m_lastViewedTab->disconnectNavigators();
461 m_lastViewedTab->setActive(false);
462 }
463 if (tabPage->splitViewEnabled() && !m_navigatorsWidget->secondaryUrlNavigator()) {
464 m_navigatorsWidget->createSecondaryUrlNavigator();
465 }
466 DolphinViewContainer *viewContainer = tabPage->activeViewContainer();
467 Q_EMIT activeViewChanged(viewContainer);
468 Q_EMIT currentUrlChanged(viewContainer->url());
469 tabPage->setActive(true);
470 tabPage->connectNavigators(m_navigatorsWidget);
471 m_navigatorsWidget->setSecondaryNavigatorVisible(tabPage->splitViewEnabled());
472 m_lastViewedTab = tabPage;
473 }
474
475 void DolphinTabWidget::tabInserted(int index)
476 {
477 QTabWidget::tabInserted(index);
478
479 if (tabBar()->isVisible()) {
480 // Resolve all pending tab icons
481 for (int i = 0; i < count(); ++i) {
482 const QUrl url = tabPageAt(i)->activeViewContainer()->url();
483 if (tabBar()->tabIcon(i).isNull()) {
484 // ensure the path url ends with a slash to have proper folder icon for remote folders
485 const QUrl pathUrl = QUrl(url.adjusted(QUrl::StripTrailingSlash).toString(QUrl::FullyEncoded).append("/"));
486 tabBar()->setTabIcon(i, QIcon::fromTheme(KIO::iconNameForUrl(pathUrl)));
487 }
488 if (tabBar()->tabToolTip(i).isEmpty()) {
489 tabBar()->setTabToolTip(index, url.toDisplayString(QUrl::PreferLocalFile));
490 }
491 }
492 }
493
494 Q_EMIT tabCountChanged(count());
495 }
496
497 void DolphinTabWidget::tabRemoved(int index)
498 {
499 QTabWidget::tabRemoved(index);
500
501 Q_EMIT tabCountChanged(count());
502 }
503
504 QString DolphinTabWidget::tabName(DolphinTabPage *tabPage) const
505 {
506 if (!tabPage) {
507 return QString();
508 }
509 // clang-format off
510 QString name;
511 if (tabPage->splitViewEnabled()) {
512 if (tabPage->primaryViewActive()) {
513 // i18n: %1 is the primary view and %2 the secondary view. For left to right languages the primary view is on the left so we also want it to be on the
514 // left in the tab name. In right to left languages the primary view would be on the right so the tab name should match.
515 name = i18nc("@title:tab Active primary view | (Inactive secondary view)", "%1 | (%2)", tabPage->primaryViewContainer()->caption(), tabPage->secondaryViewContainer()->caption());
516 } else {
517 // i18n: %1 is the primary view and %2 the secondary view. For left to right languages the primary view is on the left so we also want it to be on the
518 // left in the tab name. In right to left languages the primary view would be on the right so the tab name should match.
519 name = i18nc("@title:tab (Inactive primary view) | Active secondary view", "(%1) | %2", tabPage->primaryViewContainer()->caption(), tabPage->secondaryViewContainer()->caption());
520 }
521 } else {
522 name = tabPage->activeViewContainer()->caption();
523 }
524 // clang-format on
525
526 // Make sure that a '&' inside the directory name is displayed correctly
527 // and not misinterpreted as a keyboard shortcut in QTabBar::setTabText()
528 return KStringHandler::rsqueeze(name.replace('&', QLatin1String("&&")), 40 /* default maximum visible folder name visible */);
529 }
530
531 DolphinViewContainer *DolphinTabWidget::viewContainerAt(DolphinTabWidget::ViewIndex viewIndex) const
532 {
533 const auto tabPage = tabPageAt(viewIndex.tabIndex);
534 if (!tabPage) {
535 return nullptr;
536 }
537 return viewIndex.isInPrimaryView ? tabPage->primaryViewContainer() : tabPage->secondaryViewContainer();
538 }
539
540 DolphinViewContainer *DolphinTabWidget::activateViewContainerAt(DolphinTabWidget::ViewIndex viewIndex)
541 {
542 activateTab(viewIndex.tabIndex);
543 auto viewContainer = viewContainerAt(viewIndex);
544 if (!viewContainer) {
545 return nullptr;
546 }
547 viewContainer->setActive(true);
548 return viewContainer;
549 }
550
551 const std::optional<const DolphinTabWidget::ViewIndex> DolphinTabWidget::viewOpenAtDirectory(const QUrl &directory) const
552 {
553 int i = currentIndex();
554 if (i < 0) {
555 return std::nullopt;
556 }
557 // loop over the tabs starting from the current one
558 do {
559 const auto tabPage = tabPageAt(i);
560 if (tabPage->primaryViewContainer()->url() == directory) {
561 return std::optional(ViewIndex{i, true});
562 }
563
564 if (tabPage->splitViewEnabled() && tabPage->secondaryViewContainer()->url() == directory) {
565 return std::optional(ViewIndex{i, false});
566 }
567
568 i = (i + 1) % count();
569 } while (i != currentIndex());
570
571 return std::nullopt;
572 }
573
574 const std::optional<const DolphinTabWidget::ViewIndex> DolphinTabWidget::viewShowingItem(const QUrl &item) const
575 {
576 // The item might not be loaded yet even though it exists. So instead
577 // we check if the folder containing the item is showing its contents.
578 const QUrl dirContainingItem(item.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
579
580 // The dirContainingItem is either open directly or expanded in a tree-style view mode.
581 // Is dirContainingitem the base url of a view?
582 auto viewOpenAtContainingDirectory = viewOpenAtDirectory(dirContainingItem);
583 if (viewOpenAtContainingDirectory.has_value()) {
584 return viewOpenAtContainingDirectory;
585 }
586
587 // Is dirContainingItem expanded in some tree-style view?
588 // The rest of this method is about figuring this out.
589
590 int i = currentIndex();
591 if (i < 0) {
592 return std::nullopt;
593 }
594 // loop over the tabs starting from the current one
595 do {
596 const auto tabPage = tabPageAt(i);
597 if (tabPage->primaryViewContainer()->url().isParentOf(item)) {
598 const KFileItem fileItemContainingItem = tabPage->primaryViewContainer()->view()->items().findByUrl(dirContainingItem);
599 if (!fileItemContainingItem.isNull() && tabPage->primaryViewContainer()->view()->isExpanded(fileItemContainingItem)) {
600 return std::optional(ViewIndex{i, true});
601 }
602 }
603
604 if (tabPage->splitViewEnabled() && tabPage->secondaryViewContainer()->url().isParentOf(item)) {
605 const KFileItem fileItemContainingItem = tabPage->secondaryViewContainer()->view()->items().findByUrl(dirContainingItem);
606 if (!fileItemContainingItem.isNull() && tabPage->secondaryViewContainer()->view()->isExpanded(fileItemContainingItem)) {
607 return std::optional(ViewIndex{i, false});
608 }
609 }
610
611 i = (i + 1) % count();
612 } while (i != currentIndex());
613
614 return std::nullopt;
615 }
616
617 #include "moc_dolphintabwidget.cpp"