From edda24eb851c2647f7dde01885008ef60fcadd9a Mon Sep 17 00:00:00 2001 From: Victor Blanchard Date: Wed, 28 May 2025 12:33:52 +0200 Subject: [PATCH] dolphinview: Add a dynamic view option Added a 'dynamic view' option, which allows to switch from a 'compact' or 'details' view to an 'icons' view if most of the files in the directory are images or videos. It reverts to the previous view mode when we switch to a directory which doesn't meet that criteria. The view mode is only changed once so users don't have to undo that for specific folders when they don't want icon view. A setting is added in the "Display style" section of the general view setting page. BUG: 491139 --- ...dolphin_directoryviewpropertysettings.kcfg | 5 ++ src/settings/dolphin_generalsettings.kcfg | 4 + .../viewmodes/generalviewsettingspage.cpp | 6 ++ .../viewmodes/generalviewsettingspage.h | 1 + src/tests/dolphinmainwindowtest.cpp | 84 ++++++++++++++++++- src/views/dolphinview.cpp | 46 ++++++++++ src/views/dolphinview.h | 5 ++ src/views/viewproperties.cpp | 13 +++ src/views/viewproperties.h | 3 + 9 files changed, 166 insertions(+), 1 deletion(-) diff --git a/src/settings/dolphin_directoryviewpropertysettings.kcfg b/src/settings/dolphin_directoryviewpropertysettings.kcfg index e0c8aa1cc..bae1f409f 100644 --- a/src/settings/dolphin_directoryviewpropertysettings.kcfg +++ b/src/settings/dolphin_directoryviewpropertysettings.kcfg @@ -77,6 +77,11 @@ The last time these properties were changed by the user. + + + false + + diff --git a/src/settings/dolphin_generalsettings.kcfg b/src/settings/dolphin_generalsettings.kcfg index e950099ec..2252eed28 100644 --- a/src/settings/dolphin_generalsettings.kcfg +++ b/src/settings/dolphin_generalsettings.kcfg @@ -153,6 +153,10 @@ false + + + false + diff --git a/src/settings/viewmodes/generalviewsettingspage.cpp b/src/settings/viewmodes/generalviewsettingspage.cpp index 7caffe0f9..988f243c1 100644 --- a/src/settings/viewmodes/generalviewsettingspage.cpp +++ b/src/settings/viewmodes/generalviewsettingspage.cpp @@ -47,6 +47,8 @@ GeneralViewSettingsPage::GeneralViewSettingsPage(const QUrl &url, QWidget *paren localViewPropsLabel->setWordWrap(true); localViewPropsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + m_dynamicView = new QCheckBox(i18nc("option:check", "Use icons view mode for locations which mostly contain media files")); + QButtonGroup *viewGroup = new QButtonGroup(this); viewGroup->addButton(m_globalViewProps); viewGroup->addButton(m_localViewProps); @@ -54,6 +56,7 @@ GeneralViewSettingsPage::GeneralViewSettingsPage(const QUrl &url, QWidget *paren topLayout->addRow(QString(), globalViewPropsLabel); topLayout->addRow(QString(), m_localViewProps); topLayout->addRow(QString(), localViewPropsLabel); + topLayout->addRow(QString(), m_dynamicView); topLayout->addItem(new QSpacerItem(0, Dolphin::VERTICAL_SPACER_HEIGHT, QSizePolicy::Fixed, QSizePolicy::Fixed)); @@ -191,6 +194,7 @@ GeneralViewSettingsPage::GeneralViewSettingsPage(const QUrl &url, QWidget *paren connect(m_showSelectionToggle, &QCheckBox::toggled, this, &GeneralViewSettingsPage::changed); connect(m_renameInline, &QCheckBox::toggled, this, &GeneralViewSettingsPage::changed); connect(m_hideXtrashFiles, &QCheckBox::toggled, this, &GeneralViewSettingsPage::changed); + connect(m_dynamicView, &QCheckBox::toggled, this, &GeneralViewSettingsPage::changed); connect(m_doubleClickViewCustomAction, &QLineEdit::textChanged, this, &GeneralViewSettingsPage::changed); connect(m_doubleClickViewComboBox, qOverload(&QComboBox::currentIndexChanged), this, &GeneralViewSettingsPage::changed); connect(m_doubleClickViewComboBox, qOverload(&QComboBox::currentIndexChanged), this, &GeneralViewSettingsPage::updateCustomActionVisibility); @@ -212,6 +216,7 @@ void GeneralViewSettingsPage::applySettings() settings->setShowSelectionToggle(m_showSelectionToggle->isChecked()); settings->setRenameInline(m_renameInline->isChecked()); settings->setHideXTrashFile(m_hideXtrashFiles->isChecked()); + settings->setDynamicView(m_dynamicView->isChecked()); settings->setAutoExpandFolders(m_autoExpandFolders->isChecked()); settings->setBrowseThroughArchives(m_openArchivesAsFolder->isChecked()); settings->setDoubleClickViewCustomAction(m_doubleClickViewCustomAction->text()); @@ -246,6 +251,7 @@ void GeneralViewSettingsPage::loadSettings() m_showSelectionToggle->setChecked(GeneralSettings::showSelectionToggle()); m_renameInline->setChecked(GeneralSettings::renameInline()); m_hideXtrashFiles->setChecked(GeneralSettings::hideXTrashFile()); + m_dynamicView->setChecked(GeneralSettings::dynamicView()); m_localViewProps->setChecked(!useGlobalViewProps); m_globalViewProps->setChecked(useGlobalViewProps); diff --git a/src/settings/viewmodes/generalviewsettingspage.h b/src/settings/viewmodes/generalviewsettingspage.h index 1d4caab65..77ab004a6 100644 --- a/src/settings/viewmodes/generalviewsettingspage.h +++ b/src/settings/viewmodes/generalviewsettingspage.h @@ -51,6 +51,7 @@ private: QCheckBox *m_openArchivesAsFolder = nullptr; QCheckBox *m_autoExpandFolders = nullptr; QCheckBox *m_hideXtrashFiles = nullptr; + QCheckBox *m_dynamicView = nullptr; QComboBox *m_doubleClickViewComboBox = nullptr; QLineEdit *m_doubleClickViewCustomAction = nullptr; QLabel *m_doubleClickViewCustomActionInfo = nullptr; diff --git a/src/tests/dolphinmainwindowtest.cpp b/src/tests/dolphinmainwindowtest.cpp index b3e52ce23..345a56504 100644 --- a/src/tests/dolphinmainwindowtest.cpp +++ b/src/tests/dolphinmainwindowtest.cpp @@ -5,11 +5,11 @@ */ #include "dolphinmainwindow.h" +#include "dolphin_generalsettings.h" #include "dolphinnewfilemenu.h" #include "dolphintabpage.h" #include "dolphintabwidget.h" #include "dolphinviewcontainer.h" -#include "dolphin_generalsettings.h" #include "kitemviews/kfileitemmodel.h" #include "kitemviews/kfileitemmodelrolesupdater.h" #include "kitemviews/kitemlistcontainer.h" @@ -18,6 +18,7 @@ #include "kitemviews/kitemlistwidget.h" #include "testdir.h" #include "views/dolphinitemlistview.h" +#include "views/viewproperties.h" #include #include @@ -67,6 +68,7 @@ private Q_SLOTS: void testAutoSaveSession(); void testInlineRename(); void testThumbnailAfterRename(); + void testViewModeAfterDynamicView(); void cleanupTestCase(); private: @@ -1064,6 +1066,86 @@ void DolphinMainWindowTest::testThumbnailAfterRename() QCOMPARE(view->m_model->count(), 1); } +void DolphinMainWindowTest::testViewModeAfterDynamicView() +{ + GeneralSettings *settings = GeneralSettings::self(); + settings->setDynamicView(true); + settings->save(); + + // prepare test data + QScopedPointer testDir{new TestDir()}; + QString testDirUrl(QDir::cleanPath(testDir->url().toString())); + testDir->createDir("a"); + QImage testImage(256, 256, QImage::Format_Mono); + testImage.setColorCount(1); + testImage.setColor(0, qRgba(255, 0, 0, 255)); // Index #0 = Red + for (short x = 0; x < 256; ++x) { + for (short y = 0; y < 256; ++y) { + testImage.setPixel(x, y, 0); + } + } + testImage.save(testDir->url().path() + "/a/1.jpg"); + + // open test dir and set default view mode to "Details" + m_mainWindow->openDirectories({testDirUrl}, false); + DolphinView *view = m_mainWindow->activeViewContainer()->view(); + QSignalSpy viewDirectoryLoadingCompletedSpy(view, &DolphinView::directoryLoadingCompleted); + QSignalSpy modelDirectoryLoadingCompletedSpy(view->m_model, &KFileItemModel::directoryLoadingCompleted); + m_mainWindow->show(); + QVERIFY(viewDirectoryLoadingCompletedSpy.wait()); + QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data())); + QVERIFY(m_mainWindow->isVisible()); + m_mainWindow->actionCollection()->action(QStringLiteral("details"))->trigger(); + QCOMPARE(view->m_mode, DolphinView::DetailsView); + + // move to child folder and check that dynamic view changed view mode to icons + m_mainWindow->openFiles({testDirUrl + "/a"}, false); + view->m_model->loadDirectory(QUrl(testDirUrl + "/a")); + view->setUrl(QUrl(testDirUrl + "/a")); + QVERIFY(modelDirectoryLoadingCompletedSpy.wait()); + QCOMPARE(view->m_mode, DolphinView::IconsView); + + // go back to parent folder and check that view mode reverted to details + m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Back))->trigger(); + view->m_model->loadDirectory(testDir->url()); + view->setUrl(testDir->url()); + QVERIFY(modelDirectoryLoadingCompletedSpy.wait()); + QCOMPARE(view->m_mode, DolphinView::DetailsView); + + // test for local views + settings->setGlobalViewProps(false); + settings->save(); + + // go to child folder and check DynamicViewPassed key in view properties as well as view mode + m_mainWindow->openFiles({testDirUrl + "/a"}, false); + view->m_model->loadDirectory(QUrl(testDirUrl + "/a")); + view->setUrl(QUrl(testDirUrl + "/a")); + QVERIFY(modelDirectoryLoadingCompletedSpy.wait()); + QCOMPARE(view->m_mode, DolphinView::IconsView); + QTest::qWait(100); + QVERIFY(ViewProperties(view->viewPropertiesUrl()).dynamicViewPassed()); + + // change view mode of child folder to "Details" + m_mainWindow->actionCollection()->action(QStringLiteral("details"))->trigger(); + QCOMPARE(view->m_mode, DolphinView::DetailsView); + + // go back to parent folder + m_mainWindow->actionCollection()->action(KStandardAction::name(KStandardAction::Back))->trigger(); + view->m_model->loadDirectory(testDir->url()); + view->setUrl(testDir->url()); + QVERIFY(modelDirectoryLoadingCompletedSpy.wait()); + QCOMPARE(view->m_mode, DolphinView::DetailsView); + QVERIFY(!ViewProperties(view->viewPropertiesUrl()).dynamicViewPassed()); + + // go to child folder and make sure view mode change to "Details" is permanent + m_mainWindow->openFiles({testDirUrl + "/a"}, false); + view->m_model->loadDirectory(QUrl(testDirUrl + "/a")); + view->setUrl(QUrl(testDirUrl + "/a")); + QVERIFY(modelDirectoryLoadingCompletedSpy.wait()); + QCOMPARE(view->m_mode, DolphinView::DetailsView); + QVERIFY(ViewProperties(view->viewPropertiesUrl()).dynamicViewPassed()); +} + void DolphinMainWindowTest::cleanupTestCase() { m_mainWindow->showNormal(); diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp index bff6e7586..50c014c6a 100644 --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -1945,6 +1945,7 @@ void DolphinView::slotDirectoryLoadingCompleted() Q_EMIT directoryLoadingCompleted(); + applyDynamicView(); updatePlaceholderLabel(); updateWritableState(); } @@ -2213,6 +2214,51 @@ void DolphinView::applyModeToView() } } +void DolphinView::applyDynamicView() +{ + ViewProperties props(viewPropertiesUrl()); + /* return early if: + * - dynamic view is not enabled + * - the current view mode is already Icon View + * - dynamic view has previously changed the view mode + */ + if (!GeneralSettings::dynamicView() || m_mode == IconsView || props.dynamicViewPassed()) { + return; + } + + uint imageAndVideoCount = 0; + uint checkedItems = 0; + const uint totalItems = itemsCount(); + const KFileItemList itemList = items(); + bool applyDynamicView = false; + + for (const auto &file : itemList) { + ++checkedItems; + const QString type = file.mimetype().slice(0, 5); + + if (type == "image" || type == "video") { + ++imageAndVideoCount; + // if 2/3 or more of the items are images/videos, dynamic view should be applied + applyDynamicView = imageAndVideoCount >= (totalItems * 2 / 3); + if (applyDynamicView) { + break; + } + } else if (checkedItems - imageAndVideoCount > totalItems / 3) { + // if more than a third of the checked files are not media files, return + return; + } + } + + if (!applyDynamicView) { + return; + } + + props.setAutoSaveEnabled(!GeneralSettings::globalViewProps()); + props.setDynamicViewPassed(true); + props.setViewMode(IconsView); + applyViewProperties(props); +} + void DolphinView::pasteToUrl(const QUrl &url) { KIO::PasteJob *job = KIO::paste(QApplication::clipboard()->mimeData(), url); diff --git a/src/views/dolphinview.h b/src/views/dolphinview.h index de4bc1af2..f491b6dd5 100644 --- a/src/views/dolphinview.h +++ b/src/views/dolphinview.h @@ -867,6 +867,11 @@ private: */ void applyModeToView(); + /** + * Changes the current view based on the content of the directory. + */ + void applyDynamicView(); + enum Selection { HasSelection, NoSelection }; /** * Helper method for DolphinView::requestStatusBarText(). diff --git a/src/views/viewproperties.cpp b/src/views/viewproperties.cpp index 5dbdd938e..8e09009e5 100644 --- a/src/views/viewproperties.cpp +++ b/src/views/viewproperties.cpp @@ -369,6 +369,19 @@ bool ViewProperties::sortHiddenLast() const return m_node->sortHiddenLast(); } +void ViewProperties::setDynamicViewPassed(bool dynamicViewPassed) +{ + if (m_node->dynamicViewPassed() != dynamicViewPassed) { + m_node->setDynamicViewPassed(dynamicViewPassed); + update(); + } +} + +bool ViewProperties::dynamicViewPassed() const +{ + return m_node->dynamicViewPassed(); +} + void ViewProperties::setVisibleRoles(const QList &roles) { if (roles == visibleRoles()) { diff --git a/src/views/viewproperties.h b/src/views/viewproperties.h index 44c703482..bee1e7330 100644 --- a/src/views/viewproperties.h +++ b/src/views/viewproperties.h @@ -65,6 +65,9 @@ public: void setSortHiddenLast(bool hiddenLast); bool sortHiddenLast() const; + void setDynamicViewPassed(bool dynamicViewPassed); + bool dynamicViewPassed() const; + /** * Sets the additional information for the current set view-mode. * Note that the additional-info property is the only property where -- 2.47.3