]> cloud.milkyroute.net Git - dolphin.git/commitdiff
dolphinview: Add a dynamic view option
authorVictor Blanchard <viblanc@proton.me>
Wed, 28 May 2025 10:33:52 +0000 (12:33 +0200)
committerMéven Car <meven@kde.org>
Wed, 28 May 2025 10:33:52 +0000 (10:33 +0000)
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

src/settings/dolphin_directoryviewpropertysettings.kcfg
src/settings/dolphin_generalsettings.kcfg
src/settings/viewmodes/generalviewsettingspage.cpp
src/settings/viewmodes/generalviewsettingspage.h
src/tests/dolphinmainwindowtest.cpp
src/views/dolphinview.cpp
src/views/dolphinview.h
src/views/viewproperties.cpp
src/views/viewproperties.h

index e0c8aa1cca430a58817bcb58f69d2c3d90e8c1c3..bae1f409fff62970168a5080a375f3364cbe557d 100644 (file)
             <whatsthis context="@info:whatsthis">The last time these properties were changed by the user.</whatsthis>
         </entry>
 
+        <entry name="DynamicViewPassed" type="Bool">
+            <label context="@label">View mode changed once by dynamic view</label>
+            <default>false</default>
+        </entry>
+
         <!-- Obsolete - replaced by VisibleRoles -->
         <entry name="AdditionalInfo" type="StringList">
             <label context="@label">Additional Information</label>
index e950099ec2015cd74c9425815e4e38f708562001..2252eed2801c65451e8649a472d036f797e50c87 100644 (file)
             <label>Also hide files with application/x-trash mimetype</label>
             <default>false</default>
         </entry>
+        <entry name="DynamicView" type="Bool">
+            <label>Enable dynamic view</label>
+            <default>false</default>
+        </entry>
     </group>
     <group name="Notification Messages">
         <entry name="ConfirmOpenManyFolders" type="Bool">
index 7caffe0f9cd5f99b9af7bcdd455f5872c8e3bf36..988f243c1c4d8765753bde9e883d26465a81092f 100644 (file)
@@ -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<int>(&QComboBox::currentIndexChanged), this, &GeneralViewSettingsPage::changed);
     connect(m_doubleClickViewComboBox, qOverload<int>(&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);
index 1d4caab655077d0e2aacaf5ef54949f57f8d1c89..77ab004a609574f4028a1113a3328c314d3b266a 100644 (file)
@@ -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;
index b3e52ce2385f545abd79bc73f734049fdf3ab785..345a5650486a33f91363209915fa2c7b86587a58 100644 (file)
@@ -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 <KActionCollection>
 #include <KConfig>
@@ -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> 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();
index bff6e75860e0948b30bb45c521ca5132edb5186b..50c014c6ae9ca8aabc0424bc1563e4f5509146d4 100644 (file)
@@ -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);
index de4bc1af2ca28374c41d669e95425802f451899a..f491b6dd563ad9f79b07d27051d1eccdf6bb5d1f 100644 (file)
@@ -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().
index 5dbdd938eb2b796f4f8efea260207af413665af9..8e09009e58a2fa8ab9860ef1d9353bc3e19055f0 100644 (file)
@@ -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<QByteArray> &roles)
 {
     if (roles == visibleRoles()) {
index 44c7034823ac116449d1ed040401a4b8203998b2..bee1e7330f3bf00161c6371d05829a76d17039a8 100644 (file)
@@ -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