Search code examples
c++qtqdir

QT Multiple item selection from a list of files and folders


I'm developing a Qt C++ application in Unix and I've been trying to do something similar to what this image shows:

enter image description here

As you can see, there is a list of files and folders and a user can select multiple of them (if a folder is selected, all childs also get selected). I don't really care if the folder/file icons are shown.

I was able to create a list of QDir which stores all the files and folders paths given a root path. The problem is that I don't really know which widgets to use to design the selection panel.

By the way, the lis of QDir is a vector, but it can be easily modified to anything else.

Thanks!


Solution

  • As some users suggested, I ended up using QFileSystemModel. I'm gonna give a full description of how I implemented it, in case someone else comes up with this problem and needs a clear response.

    First of all, a QFileSystemModel is a file tree without checkboxes, to add them, a new class which extends QFileSystemModel and at least 3 methods must be overriden.

    class FileSelector : public QFileSystemModel
    {
    public:
        FileSelector(const char *rootPath, QObject *parent = nullptr);
        ~FileSelector();
    
        bool setData(const QModelIndex& index, const QVariant& value, int role);
        Qt::ItemFlags flags(const QModelIndex& index) const;
        QVariant data(const QModelIndex& index, int role) const;
    
    private:
        QObject *parent_;
        /* checklist_ stores all the elements which have been marked as checked */
        QSet<QPersistentModelIndex> checklist_;
    };
    

    When creating the model a flag, to indicate that it should have a checkable box, must be set. This is why we will use the flags function:

    Qt::ItemFlags FileSelector::flags(const QModelIndex& index) const
    {
        return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
    }
    

    When a click is made in the checkbox, the method setData will be called, with the index of the element that was clicked (not the checkbox itself, but the :

    bool FileSelector::setData(const QModelIndex& index, const QVariant& value, int role)
    {
        if (role == Qt::CheckStateRole && index.column() == 0) {
            QModelIndexList list;
            getAllChildren(index, list); // this function gets all children 
            // given the index and saves them into list (also saves index in the head)
            if(value == Qt::Checked)
            {
                for(int i = 0; i < list.size(); i++)
                {
                   checklist_.insert(list[i]);
                   // signals that a change has been made
                   emit dataChanged(list[i], list[i]);
                }
            }
            else if(value == Qt::Unchecked)
            {
                for(int i = 0; i < list.size(); i++)
                {
                    checklist_.remove(list[i]);
                    emit dataChanged(list[i], list[i]);
                }
            }
            return true;
        }
        return QFileSystemModel::setData(index, value, role);
    }
    

    When dataChanged is signaled or you open a new path of the tree, the data function will be called. Here you have to make sure to only display the checkbox at the first column (next to the filename), and to retrieve the state of the checkbox, to mark it as checked/unchecked.

    QVariant FileSelector::data(const QModelIndex& index, int role) const
    {
        if (role == Qt::CheckStateRole && index.column() == 0) {
            if(checklist_.contains(index)) return Qt::Checked;
            else return Qt::Unchecked;
        }
        return QFileSystemModel::data(index, role);
    }
    

    The only thing I was not able to accomplish was getting all childs, since the folders must be open to retrieve the childs. So a closed folder won't have any child until you open it.

    Hope this can help someone who has the same problem as I did!