Search code examples
qmlkde-plasma

DelegateModelGroup filtering DelegateModel


Below is the DelegateModel I am using to filter a ListModel.I have used five DelegateModelGroup's.The filtering is working for visible , folder and falsie DeleagteModelGroup.Now i want to implement a new DelegateModelGroup, visibility with which i want to be visible only the model items that have visibility role false.I tried a lot examples but I had not have the desired result.How can I configure the update function so I can filter the results to visibility role?

visible DelegateModelGroup is filtered for false.

folder DelegateModelGroup is filtering the items if folder role is empty string or not.

falsie DeleagetModelGroup filters the items so they displayed in pages of 35 items.

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQml.Models 2.4

DelegateModel {
    property string roleThree
    property string roleTwo
    property string role
    property int from: 0
    property int to: 10

    onRoleChanged: Qt.callLater(update)
    onFromChanged: Qt.callLater(update)
    onToChanged: Qt.callLater(update)

    groups: [
        DelegateModelGroup {
            id: newItems
            name: "new"
            includeByDefault: true
            onCountChanged:{
                 Qt.callLater(update)
            }
        },

        DelegateModelGroup {
            id: visibleItems
            name: "visible"
            includeByDefault: false
        },

        DelegateModelGroup {
            id: folderItems
            name: "folder"
            includeByDefault: false
        },

        DelegateModelGroup {
            id: visibilityItems
            name: "visibility"
            includeByDefault: false
        },

        DelegateModelGroup {
            id: falsieItems
            name: "falsie"
            includeByDefault: false
        }
    ]

    filterOnGroup: "falsie"

    function update() {
        newItems.setGroups(0, newItems.count, [ "new" ] );
                    for (let i = 0; i < newItems.count; i++) {
                        let item = newItems.get(i).model;
                        let visible = item[role] === false ;
                        let folder = item[roleTwo] !== "";
                        let visibility = item[roleThree] ===false;
                    if (!visible && !folder && visibility) continue;
                newItems.setGroups(i, 1, [ "new", "visible" ]);
                visibleItems.setGroups(i, 1, [ "visible","folder"]);

                }
        visibleItems.setGroups(from, to - from , ["visible","falsie"]);
    }
     Component.onCompleted: Qt.callLater(update)

}

For any question fill free to comment.

Thanks in advance.


Solution

  • I've updated the pattern I've used for DelegateModelGroup.

    I tend to have "all" which is a master list of all items, "new" which is a list of incoming items, and then DelegateModelGroups for your business rules.

    I have two functions

    • invalidate() - which pushes everything back into the "new" group for processing
    • updateNew() - which pulls an item from the "new" group, 1 item at a time, for filtering

    The reason why we are processing the newItems from count-1 backward to 0 is every item being processed will get removed from the newItems list. If we went in ascending order it messes up the for loop and it processes every second record instead of every record. So, by processing it in descending order, we remove items from the end and get to process every item.

    DelegateModelGroup {
        groups: [
            DelegateModelGroup {
                id: allItems
                name: "all"
                includeByDefault: true
            },
            DelegateModelGroup {
                id: newItems
                name: "new"
                includeByDefault: true
                onChanged: Qt.callLater(updateNew)
            },
            DelegateModelGroup { name: "visible" }
            DelegateModelGroup { name: "folder" }
            DelegateModelGroup { name: "visibility" }
            DelegateModelGroup { name: "falsie" }
        ]
    
        filterOnGroup: "visible"
        function invalidate() {
            allItems.setGroups(0, allItems.count, ["all","new"]);
        }
        function updateNew() {
            if (!newItems.count) return;
            for (let index = newItems.count-1; index >= 0; index--) {
                let item = newItems.get(index).model;
                let grps = [ "all" ];
                if (item[role] === false) grps.push("visible");
                if (item[rowTwo] !== "") grps.push("folder");
                if (item[roleThree] === false) grps.push("visibility");
                newItems.setGroups(index, 1, grps);
            }
        }
    }
    

    [EDIT]

    In the following example, I have created FilterDelegateModel.qml

    // FilterDelegateModel.qml
    DelegateModel {
        property string role
        property string filter
        property double from
        property double to
        property bool invert
        property int page
        property int pageSize
        //...
    }
    

    which handles both strings (for indexOf searches) and numbers (for from...to range). I made role, filter, from, to, invert, page and pageSize as configurable inputs, which, when changed, will cause the "visible" and "page" DelegateModelGroups to refresh.

    role can be "country", "city", or "population". If it's the first two, it will limit the results by "filter" using indexOf(). If you set it to "population" it will use "from" and "to" to limit your results by range. The results will appear in the "visible" DelegateModelGroup.

    page and pageSize is used to further filter the results by what results is visible on the current page. These result will appear in the "page" DelegateModelGroup.

    import QtQuick
    import QtQuick.Controls
    import QtQuick.Layouts
    Page {
        ColumnLayout {
            anchors.fill: parent
            Label { text: "Role" }
            ComboBox { id: roleCombo; model: [ "country", "city", "population" ] }
            ColumnLayout {
                visible: roleCombo.currentText.match(/country|city/)
                Label { text: "Search" }
                TextField { id: search }
            }
            ColumnLayout {
                visible: roleCombo.currentText === 'population'
                Label { text: "Range %1 to %2".arg(range.first.value).arg(range.second.value) }
                RangeSlider { id: range; from: 0; to: 2000; stepSize: 10; first.value: 0; second.value: 2000 }
            }
            CheckBox { id: chkInvert; text: "Invert" }
            Label { text: "Page" }
            ComboBox { id: pageCombo; model: ["1","2","3","4","5"] }
            Label { text: "Cities (count:%1 of %2)"
                        .arg(filterDelegateModel.pageCount)
                        .arg(filterDelegateModel.visibleCount) }
            ListView {
                Layout.fillWidth: true
                Layout.fillHeight: true
                clip: true
                header: RowLayout {
                    width: ListView.view.width
                    Text { Layout.preferredWidth: 30; Layout.fillWidth: true; text: "Index" }
                    Text { Layout.preferredWidth: 100; Layout.fillWidth: true; text: "Country" }
                    Text { Layout.preferredWidth: 100; Layout.fillWidth: true; text: "City"
     }
                    Text { Layout.preferredWidth: 70; Layout.fillWidth: true; text: "Population" }
                }
                model: FilterDelegateModel {
                    id: filterDelegateModel
                    model: Cities { }
                    role: roleCombo.currentText
                    filter: search.text
                    invert: chkInvert.checked
                    from: range.first.value
                    to: range.second.value
                    page: pageCombo.currentIndex
                    delegate: RowLayout {
                        width: ListView.view.width
                        Text { Layout.preferredWidth: 30; Layout.fillWidth: true; text: DelegateModel.visibleIndex + 1 }
                        Text { Layout.preferredWidth: 100; Layout.fillWidth: true; text: model.country }
                        Text { Layout.preferredWidth: 100; Layout.fillWidth: true; text: model.city }
                        Text { Layout.preferredWidth: 70; Layout.fillWidth: true; text: model.population.toFixed(1) }
                    }
                }
            }
        }
    }
    
    // FilterDelegateModel.qml
    import QtQuick
    import QtQuick.Controls
    import QtQml.Models
    DelegateModel {
        property string role
        onRoleChanged: Qt.callLater(invalidate)
        property string filter
        onFilterChanged: Qt.callLater(invalidate)
        property double from: 0
        onFromChanged: Qt.callLater(invalidate)
        property double to: 1.0
        onToChanged: Qt.callLater(invalidate)
        property bool invert: false
        onInvertChanged: Qt.callLater(invalidate)
        property int page: 0
        onPageChanged: Qt.callLater(invalidate)
        property int pageSize: 10
        onPageSizeChanged: Qt.callLater(invalidate)
        readonly property alias visibleCount: visibleItems.count
        readonly property alias pageCount: pageItems.count
        groups: [
            DelegateModelGroup {
                id: allItems
                name: "all"
                includeByDefault: true
            },
            DelegateModelGroup {
                id: newItems
                name: "new"
                includeByDefault: true
                onChanged: Qt.callLater(updateNew)
            },
            DelegateModelGroup {
                id: visibleItems
                name: "visible"
                onChanged: Qt.callLater(updatePage)
            },
            DelegateModelGroup {
                id: pageItems
                name: "page"
            }
        ]
        filterOnGroup: "page"
        function invalidate() {
            allItems.setGroups(0, allItems.count, [ "all","new" ] );
        }
        function updateNew() {
            for (let index = newItems.count-1; index >= 0; index--) {
                let item = newItems.get(index);
                let groups = ["all"];
                let visible = true;
                if (role) {
                    let value = item.model[role];
                    switch (typeof(value)) {
                    case 'string':
                        visible = !filter || value.toLowerCase().indexOf(filter.toLowerCase()) !== -1;
                        break;
                    case 'number':
                        visible = value >= from && value <= to;
                        break;
                    }           
                }
                if (invert) visible = !visible;
                if (visible) groups.push("visible");
                newItems.setGroups(index, 1, groups);
            }
        }
        function updatePage() {
            let pageStart = page * pageSize;
            let pageEnd = pageStart + pageSize;
            if (pageStart < 0) pageStart = 0;
            if (pageStart >= visibleItems.count) return;
            if (pageEnd > visibleItems.count) pageEnd = visibleItems.count;
            visibleItems.setGroups(pageStart, pageEnd-pageStart, ["all", "visible", "page"] );
        }
    }
    
    // Cities.qml
    import QtQuick
    import QtQuick.Controls
    ListModel {
        property var cities: `Afghanistan,Kabul,39835428
    Argentina,Buenos Aires,45993511
    Australia,Canberra,25766340
    Bangladesh,Dhaka,166303498
    Brazil,Brasília,213993437
    Canada,Ottawa,37742154
    China,Beijing,1444216107
    Colombia,Bogotá,51107063
    Czech Republic,Prague,10708981
    Egypt,Cairo,104258327
    France,Paris,65480710
    Germany,Berlin,83190556
    Greece,Athens,10715549
    India,New Delhi,1393409038
    Indonesia,Jakarta,273523615
    Iran,Tehran,83290141
    Italy,Rome,60550075
    Japan,Tokyo,125960000
    Mexico,Mexico City,129163276
    Netherlands,Amsterdam,17141544
    Nigeria,Abuja,214028302
    Pakistan,Islamabad,233500636
    Peru,Lima,33405000
    Philippines,Manila,111046913
    Poland,Warsaw,38028278
    Portugal,Lisbon,10191409
    Russia,Moscow,146599183
    Saudi Arabia,Riyadh,35541063
    South Africa,Pretoria,61622110
    South Korea,Seoul,51780579
    Spain,Madrid,46723749
    Sri Lanka,Colombo,21413249
    Sweden,Stockholm,10171524
    Switzerland,Bern,8741992
    Thailand,Bangkok,69799978
    Turkey,Ankara,85042739
    Ukraine,Kyiv,44622516
    United Kingdom,London,66647112
    United States,Washington D.C.,332915073
    Vietnam,Hanoi,97338579`.split(/\n/).map(c => c.split(/,/))
        Component.onCompleted: {
            for (let c of cities) append( { "country":c[0], "city":c[1], "population":c[2]/1000000 } )
        }
    }
    

    You can Try it Online!