┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMāris Nartišs <[email protected]>2026-03-18 10:56:36 +0000
committerMéven Car <[email protected]>2026-03-18 10:56:36 +0000
commita7810673de828fec0861c11062491adaa85c8a00 (patch)
tree3fa74b0ee96cb0e8bcd2526f4407937ff81f74a0
parenta7cc959d5f1311ae928dd077b376570351ef5259 (diff)
DolphinTabPage: Prevent re-entrant signal activation for slotViewActivated
When switching tabs with split view enabled and the filter bar visible, a storm of activation calls is triggered and at the end signal/slot connections for both views is a mess (no listeners, navigation buttons linked to unfocused view etc.). Disconnect DolphinTabPage slot when it changes its own state. BUG: 508554, 512011, 508405, 511076, 503576
-rw-r--r--src/dolphintabpage.cpp42
-rw-r--r--src/dolphintabpage.h9
-rw-r--r--src/tests/dolphinmainwindowtest.cpp48
3 files changed, 98 insertions, 1 deletions
diff --git a/src/dolphintabpage.cpp b/src/dolphintabpage.cpp
index ca47130c0..f4922bf70 100644
--- a/src/dolphintabpage.cpp
+++ b/src/dolphintabpage.cpp
@@ -60,7 +60,13 @@ DolphinTabPage::DolphinTabPage(const QUrl &primaryUrl, const QUrl &secondaryUrl,
m_secondaryViewContainer->show();
}
+ // DolphinView::setActive(true) calls setFocus() then emits activated().
+ // activated() is connected to slotViewActivated() which toggles
+ // m_primaryViewActive — correct for user-initiated pane switches, but
+ // wrong here during construction. Disconnect to prevent the spurious toggle.
+ disconnectViewActivatedSignals();
m_primaryViewContainer->setActive(true);
+ connectViewActivatedSignals();
}
bool DolphinTabPage::primaryViewActive() const
@@ -364,12 +370,22 @@ void DolphinTabPage::restoreState(const QByteArray &state)
}
stream >> m_primaryViewActive;
+ // DolphinView::setActive(true) calls setFocus() then emits activated().
+ // activated() is connected to slotViewActivated() which toggles
+ // m_primaryViewActive — correct for user-initiated pane switches, but
+ // wrong here during session restore. Disconnect to prevent the spurious toggle.
+ disconnectViewActivatedSignals();
if (m_primaryViewActive) {
+ if (m_splitViewEnabled) {
+ m_secondaryViewContainer->setActive(false);
+ }
m_primaryViewContainer->setActive(true);
} else {
Q_ASSERT(m_splitViewEnabled);
+ m_primaryViewContainer->setActive(false);
m_secondaryViewContainer->setActive(true);
}
+ connectViewActivatedSignals();
QByteArray splitterState;
stream >> splitterState;
@@ -393,8 +409,16 @@ void DolphinTabPage::setActive(bool active)
// we should bypass changing active view in split mode
m_active = !m_splitViewEnabled;
}
- // we want view to fire activated when goes from false to true
+ // DolphinView::setActive(true) calls setFocus() then emits activated().
+ // activated() is connected to slotViewActivated() which toggles
+ // m_primaryViewActive — correct for user-initiated pane switches, but
+ // wrong here during tab switch. Disconnect to prevent the spurious toggle.
+ disconnectViewActivatedSignals();
+ if (active && m_splitViewEnabled) {
+ inactiveViewContainer()->setActive(false);
+ }
activeViewContainer()->setActive(active);
+ connectViewActivatedSignals();
}
void DolphinTabPage::setCustomLabel(const QString &label)
@@ -518,6 +542,22 @@ void DolphinTabPage::switchActiveView()
}
}
+void DolphinTabPage::connectViewActivatedSignals()
+{
+ connect(m_primaryViewContainer->view(), &DolphinView::activated, this, &DolphinTabPage::slotViewActivated);
+ if (m_secondaryViewContainer) {
+ connect(m_secondaryViewContainer->view(), &DolphinView::activated, this, &DolphinTabPage::slotViewActivated);
+ }
+}
+
+void DolphinTabPage::disconnectViewActivatedSignals()
+{
+ disconnect(m_primaryViewContainer->view(), &DolphinView::activated, this, &DolphinTabPage::slotViewActivated);
+ if (m_secondaryViewContainer) {
+ disconnect(m_secondaryViewContainer->view(), &DolphinView::activated, this, &DolphinTabPage::slotViewActivated);
+ }
+}
+
DolphinViewContainer *DolphinTabPage::createViewContainer(const QUrl &url) const
{
DolphinViewContainer *container = new DolphinViewContainer(url, m_splitter);
diff --git a/src/dolphintabpage.h b/src/dolphintabpage.h
index b6135d360..421496ce9 100644
--- a/src/dolphintabpage.h
+++ b/src/dolphintabpage.h
@@ -204,6 +204,15 @@ private:
*/
void startExpandViewAnimation(DolphinViewContainer *expandingContainer);
+ /**
+ * Connect/disconnect DolphinView::activated → slotViewActivated for all
+ * current view containers. Used to suppress the signal during programmatic
+ * activation (tab switch, construction, session restore) so that
+ * slotViewActivated does not spuriously toggle m_primaryViewActive.
+ */
+ void connectViewActivatedSignals();
+ void disconnectViewActivatedSignals();
+
private:
DolphinTabPageSplitter *m_splitter;
diff --git a/src/tests/dolphinmainwindowtest.cpp b/src/tests/dolphinmainwindowtest.cpp
index 26bb52a8e..a1727afdb 100644
--- a/src/tests/dolphinmainwindowtest.cpp
+++ b/src/tests/dolphinmainwindowtest.cpp
@@ -71,6 +71,7 @@ private Q_SLOTS:
void testThumbnailAfterRename();
void testViewModeAfterDynamicView();
void testActivationAndTabTitleAfterRenameOpeningFolder();
+ void testActiveViewAfterTabSwitchWithSplitView();
void cleanupTestCase();
private:
@@ -1292,6 +1293,53 @@ void DolphinMainWindowTest::testActivationAndTabTitleAfterRenameOpeningFolder()
QCOMPARE(tabWidget->tabText(1), expectedNewTab1Title);
}
+// Test that switching tabs does not spuriously toggle which split-view pane is active.
+// Regression test for the bug where DolphinTabPage::setActive(true) during tab switch
+// caused DolphinView::activated() to reach slotViewActivated(), which toggled
+// m_primaryViewActive and connected MainWindow signals to the wrong view container.
+void DolphinMainWindowTest::testActiveViewAfterTabSwitchWithSplitView()
+{
+ m_mainWindow->openDirectories({QUrl::fromLocalFile(QDir::homePath())}, false);
+ m_mainWindow->show();
+ QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data()));
+ QVERIFY(m_mainWindow->isVisible());
+
+ auto tabWidget = m_mainWindow->findChild<DolphinTabWidget *>("tabWidget");
+ QVERIFY(tabWidget);
+
+ // Enable split view on the first tab. After this, the secondary (right) pane
+ // becomes active via slotViewActivated(), so primaryViewActive() is false.
+ m_mainWindow->actionCollection()->action(QStringLiteral("split_view"))->trigger();
+ QVERIFY(tabWidget->currentTabPage()->splitViewEnabled());
+ QVERIFY(!tabWidget->currentTabPage()->primaryViewActive());
+ auto firstTabPage = tabWidget->currentTabPage();
+ auto firstTabSecondary = firstTabPage->secondaryViewContainer();
+ QVERIFY(firstTabSecondary->isActive());
+
+ // Open a second tab and switch to it.
+ tabWidget->openNewActivatedTab(QUrl::fromLocalFile(QDir::homePath()));
+ QCOMPARE(tabWidget->count(), 2);
+ QCOMPARE(tabWidget->currentIndex(), 1);
+
+ // Spy on activeViewChanged to count emissions during the tab switch back.
+ QSignalSpy activeViewChangedSpy(tabWidget, &DolphinTabWidget::activeViewChanged);
+
+ // Switch back to the first tab.
+ tabWidget->setCurrentIndex(0);
+ QCOMPARE(tabWidget->currentTabPage(), firstTabPage);
+
+ // activeViewChanged must be emitted exactly once — by currentTabChanged itself.
+ // A spurious second emission would indicate slotViewActivated() fired during
+ // the programmatic setActive(true) and toggled m_primaryViewActive.
+ QCOMPARE(activeViewChangedSpy.count(), 1);
+
+ // The secondary pane must still be the designated active one.
+ QVERIFY(!firstTabPage->primaryViewActive());
+ QCOMPARE(firstTabPage->activeViewContainer(), firstTabSecondary);
+ QVERIFY(firstTabSecondary->isActive());
+ QVERIFY(!firstTabPage->primaryViewContainer()->isActive());
+}
+
void DolphinMainWindowTest::cleanupTestCase()
{
m_mainWindow->showNormal();