QFileSystemModel - iterating through current index children



  • I subclass QFileSystemModel to populate a root directory and display a checkbox in front of column 0 node. I want to interate through the current node´s children and set the check value based on its parent´s value but QModelIndex does not contain a children´s objectlist. Is there a way to do this?

    @
    bool JibeFileSystemModel::setData(const QModelIndex& index, const QVariant& value, int role)
    {
    if (index.isValid() && index.column() == 0 && role == Qt::CheckStateRole)
    {
    if (isDir(index))
    {
    if (value == Qt::Checked)
    checklist.insert(index);
    else
    checklist.remove(index);

            emit dataChanged(index, index);
            
            if (hasChildren(index))  // do recursive to check all child node
            {
                Q_FOREACH( QObject* obj, index.children) // does not exist!!!
                {
                    setData(, value, role);
                }
            }
        }
        else 
        {
            if (value == Qt::Checked)
                checklist.insert(index);
            else
                checklist.remove(index);
    
            emit dataChanged(index, index);
        }
        return true;
    }
    return QFileSystemModel::setData(index, value, role);
    

    }
    @



  • Replace line 14 to 20 with this:

    @
    for(int childRow = 0; childRow < rowCount(index); ++childRow) {
    QModelIndex childIndex = index(r, 0); // or whatever column you need
    setData(childIndex, ....);
    emit dataChanged(childIndex, childIndex);
    }
    @

    You might want to put that into a separate method so that you can recurse into the child tree unlimitedly.



  • Thanks Volker... however, this does not automatically check non-expanded child nodes? Any ideas?



  • Expanded/collapsed is a property of the view, not the model. You can have the very same model on different views with the same index expanded in one and collapsed in another.

    You can call "canFetchMore() ":http://doc.qt.nokia.com/4.7/qabstractitemmodel.html#canFetchMore to check if there are possibly children on this index and if so call "fetchMore() ":http://doc.qt.nokia.com/4.7/qabstractitemmodel.html#fetchMore to actually load the data. Once this is done, rowCount() gives you the correct value.

    But watch out! This loads all files and dirs below your parent if you do it recursively, which can be a huge amount of data (or the entire file system if you're on the root directory)!



  • hi volker... i tried the following but canFetchMore alway return false.

    @
    bool JibeFileSystemModel::setData(const QModelIndex& index, const QVariant& value, int role)
    {
    if (index.isValid() && index.column() == 0 && role == Qt::CheckStateRole)
    {
    if (value == Qt::Checked)
    checklist.insert(index);
    else
    checklist.remove(index);

        emit dataChanged(index, index);
    
        if (canFetchMore(index))
            fetchMore(index);
    
        for(int childRow = 0; childRow < rowCount(index); ++childRow)
        {
            QModelIndex childIndex = index.child(childRow, 0); // or whatever column you need
            setData(childIndex, value, role);
        }
    
        return true;
    }
    return QFileSystemModel::setData(index, value, role);
    

    }
    @



  • nevermind Volker... I had to override the canFetchMore function to get it to work. However, can you explain my why I am getting inconsistent results from fetchMore. I placed some debug code to test my code as below. However, eventhough I click on the same node to execute my test, I get different results printed to the output screen.

    @
    bool JibeFileSystemModel::canFetchMore ( const QModelIndex& index ) const
    {
    return hasChildren(index);
    }

    bool JibeFileSystemModel::setData(const QModelIndex& index, const QVariant& value, int role)
    {
    if (index.isValid() && index.column() == 0 && role == Qt::CheckStateRole)
    {
    if (value == Qt::Checked)
    checklist.insert(index);
    else
    checklist.remove(index);

        emit dataChanged(index, index);
    
        qDebug() << fileName(index);    ///  test code!!!!!!!!!
        
        if (canFetchMore(index))
            fetchMore(index);
    
        for(int childRow = 0; childRow < rowCount(index); ++childRow)
        {
            QModelIndex childIndex = index.child(childRow, 0); // or whatever column you need
            setData(childIndex, value, role);
        }
    
        return true;
    }
    return QFileSystemModel::setData(index, value, role);
    

    }
    @



  • If you do not implement fetchMore() and its buddies, then you shall not implement canFetchMore() as it's not you who decides this things.

    hasChildren() returns true if the node represents a directory, which is true regardless wether the children have been loaded or not.

    You can safely call fetchMore(index), if there children are already loaded it does nothing, if not it loads them.



  • based on your response, if I comment line 19 from above code, I should be able to simply use fetchMore(index) to get the correct rowCount(index) despite the index being expanded or not, right? However, I commented the code as below but still get inconsistent result for rowCount(index).

    @
    if (index.isValid() && index.column() == 0 && role == Qt::CheckStateRole)
    {
    if (value == Qt::Checked)
    checklist.insert(index);
    else
    checklist.remove(index);

        emit dataChanged(index, index);
    
        qDebug() << fileName(index);
    
        //if (canFetchMore(index))
        fetchMore(index);
    
        for(int childRow = 0; childRow < rowCount(index); ++childRow)
        {
            QModelIndex childIndex = index.child(childRow, 0); // or whatever column you need
            setData(childIndex, value, role);
        }
    
        return true;
    }
    return QFileSystemModel::setData(index, value, role);
    

    @



  • The problem may be, that AFAIK, QFileSystemModel does its fetching of nodes in a background thread. That could mean that while fetchMore(index) triggers such a background scan, the model is not actually modified until that background thread is done doing its magic. The call to fetchMore(index) would simply return immediately, and rowCount(index) on line 15 would not be updated yet.

    Note that the above is just conjecture. I did not try this myself, nor did I take a peek at the source code.



  • [quote author="Andre" date="1302679754"]The problem may be, that AFAIK, QFileSystemModel does its fetching of nodes in a background thread. That could mean that while fetchMore(index) triggers such a background scan, the model is not actually modified until that background thread is done doing its magic. The call to fetchMore(index) would simply return immediately, and rowCount(index) on line 15 would not be updated yet.

    Note that the above is just conjecture. I did not try this myself, nor did I take a peek at the source code. [/quote]

    I think it works exactly this way.
    If you have a QFileSystemModel connected to a QTreeView and open folders on slow drives, the content comes in part by part.



  • this seems to be what is happening because over time, there are more and more nodes being displayed. Is there an indication to when the fetch is complete? Better yet, what would be a better methodology for accomplishing my task? As you can see, I am trying to recursively check/uncheck all children nodes when the parent node is checked/unchecked. Also, there will be logic to uncheck any parent node if any of its child is unchecked. Thanks!



  • I would start to re-think your logic, I think. Think if you can come up with a method that does not require that every node is loaded.

    You could, for instance, do something like this:
    Each leaf node can have three states:

    Checked

    Unchecked

    Determined by parent

    A fourth state is added for each branch-node: Determined by children

    The default, implicit state for each node with the exception of the root node would be that it is determined by its parent. Note that if a branch-node's state is determined by its children, it can get the "partially checked" ambivalent value.

    If your user checks or unchecks a branch node that was determined by its parent before, you only need to do three things:

    Set the state for this branch to the appropriate explicit new state checked or unchecked

    Set the state of the siblings of this branch to reflect the (common) parents' state

    Set the parents to the "determined by children" state

    If your user checks or unchecks a branch node that was determined by its children before, you need to:

    Set the new state on the branch node

    Set iteratively set each (existing!) child node state to "Determined by parent" (or, dump that part of your data, keeping in mind that we agreed that "Determined by parent" is the default state anyway.

    For determining the actual visualized state of a check box, you end up with something like this:

    • If the state is "determined by parent", the state is the same as for the parent node
    • If the state is one of the explicit states, then the state is clear
    • If the state is "determined by children", then iterate over the (existing!) child nodes. If they are all the same, then the state is that state, otherwise the state is the ambivalent PartlyChecked state.

    Of course, the above picture is not complete. It is just to illustrate a way you can add check boxes to a tree without having to have access to the full tree. Observe that in the case of your QFileSystemModel tree model, you will probably only need to keep track of the states of just a few nodes. The rest of them will be determined implicitly. Still, to the user, checking a single directory will appear to check perhaps thousands of files under it, without the code actually having to iterate into the complete directory structure under it.



  • bq. If the state is “determined by children”, then iterate over the (existing!) child nodes. If they are all the same, then the state is that state, otherwise the state is the ambivalent PartlyChecked state.

    Thanks for your response. However, a concern I have is that I will still have to fetchMore (an indefinite procedure) to iterate over the (existing) child nodes.



  • No, you don't. That's the whole beauty of the idea. :-)

    You only need to iterate over the children that you already have. Potential children that have not been loaded yet, will have the default Determined by parent state.

    Edit:
    Perhaps there could be a slight snag though, if you are in a large directory, perhaps it could occur that not all files in that directory are loaded yet when you start modifying check boxes in it. That might be a point of breakdown. That could be solved by introducing something like a "default state for child nodes" for all branch nodes. Set that, and have nodes that have just been added take over that state.



  • Can you provide code snippets to resolve this issue? I cannot get this to work... I´ve also been searching the web for sample but have not had any luck... Thanks!



  • I could in principle, but I don't think I will have time to provide one within the next two days.



  • any help would greatly be appreciated!!! I´ve tried but cannot seem to get things to work properly. Thank you for your continual support!



  • Please do remind me (by replying in this topic) if I don't respond before the end of the week. In the meantime, of course everyone else is welcome to pitch in as well ;-)



  • Just so that we are on the same page because the topic doesn´t describe my issue in details. I am using a QTreeView populated with a subclassed QFileSystemModel. I´ve been successful at integrating a checkbox in column 0 of each tree node (a row) and have also added another column to my treeview. I am running into the issue of propagating the state of the parent node to the children when it´s state change as well as propagating the state of the parent node to uncheck if any of its child is unchecked or to check if all of its child is checked. Eventually, my goal will be to iterate through all of the check nodes (files) in the treeview and process them.



  • Andre... this is a reminder to respond on request for code snippets for propagating QTreeView using subclassed QFileSystemModel with checkboxes. Thanks!



  • It is still on my radar, but time is flying and to be honest, I'm also a bit bussy enjoying the excellent weather out here for a bit ;-) Thanks for the reminder though!



  • Hi Andre... Any progress?



  • I am making good progress. I am just trying to find some last issues, but the basics are working just fine. I guess I also need to bolt some kind of API onto it for modifying and querying the checked items from code, but that is for later.



  • The snippets you requested have turned into a complete class. The principle works, -but there is still an issue to iron out that sometimes, an infinate recursion is triggered and the application crashes. That, of course, is not acceptable.-

    Still, working with a QFileSystemModel as the base model, the proxy nicely manages to add checkboxes to each node, and make branch nodes work as expected (checking or unchecking them will do the same for the whole subtree, while having both checked and unchecked nodes in the subtree results in the Partially Checked checkbox state). That works with a minimum number of items that are actually tracked. In a realistic scenario on a pretty big file system, I still only had a few dozen items in the internal data store that tracks the check state of each node. The proxy model automatically reduces the number of size of the internal data store to a minimum. Obviously, all that works without recursing into the base model more deeply than the user already has, let alone trying to iterate over all children and trying to make the model fetch all of them. :-)

    Now that I have a complete class, the question arises under what licence to publish it. Do you need it for an open source project, or do you need something compatible with a closed source application? It turned out to be quite a bit of work to get working, even though the principles are actually the same as I described on the previous page of this topic. That makes me a bit reluctant to just dump it into the public domain...

    Edit:
    I have managed to resolve the issue, I think. At least the class now seems to work reliably under my tests. See screenshot below for how it looks:
    !http://dl.dropbox.com/u/16442531/CheckableProxyModelDemo.png(Screenshot of CheckableProxyModel class applied to a QFileSystemModel)!

    What kind of additional API would be needed, you think? I am thinking along these lines:

    An API to set the default state of all the nodes to either checked or unchecked (done)

    An API to list the checked subtrees (QModelIndexList with the top nodes that have everything under them checked) (done)

    An API to list the checked branch nodes (also a QModelIndexList) (done)

    Signals to indicate when 1 or 2 changes (done, though only one change signal for now)

    ... ?

    Will we need convenience API to set the check-state programatically? I mean, you can already do that of course by using QAbstractItemModel::setData() just like the view does, but perhaps that could be made easier. What might be handy is to be able to specify the check state based on the source models QModelIndex-es. That way, it would be easy to set for instance the check state based on the file path when using a QFileSystemModel as the source model.



  • OK, I guess the class is pretty much done. All the ideas in my previous post have been implemented, and the class seems to behave nicely even when you also use the sort capabilities of the QSortFilterProxyModel it is based on.



  • Hi Andre,

    did you think of making this class a contribution to Qt? Or a QtSolution or something. It sounds really interesting.



  • I am thinking of the best way to do this, yes. I'm not sure if it should be a Qt contribution, or some other third party component, nor I am sure if I could perhaps make some kind of dual licence for it.

    Edit: a suggestion at #qt-labs was to make it into a Qt example. That might work.



  • Hi Andre, it looks great and I can´t wait to see its implementation!!! I do not need it to be closed source, thus, any license you see fit should work.



  • OK. I guess I will start by tagging the files as GPL for now, and then put the whole project into a git repository somewhere, and I'll put the link here.

    phamtv: do you think you will be needing other API than what's already available?



  • How hard will it be to get a list of unchecked files from the root directory filtered by a certain extention.

    For instance, you´ve filter the model to show all .txt file in the tree. I see that you have all of the checked .txt file listed in the snapshot shown above. How hard will it to be to get all the .txt file that is NOT checked from your model?

    edit: The goal is to get a list of all UNCHECKED filtered files from -partially checked/checked- all directory.



  • Hmmm... That is actually not trivial. I guess you can do the filtering on .txt files using the QFileSystemModel itself, by using the setNameFilter.

    In principle, getting the unchecked items from the tree is about the same complexity as getting the checked ones: doable. That is: you would get a list of unchecked files, and of unchecked branches.
    However, it is not really feasable to get all the unchecked .txt files inside the unchecked branches. That would bring you back to having to populate the entire tree and potentially recurse through the whole file system to find them all. That would be outside the scope of this class, and should be done using QDirIterator, I think (if you really can't avoid this) or using system dependent methods if your system supports more efficient search of files than just scanning the complete file system. You can still use the results of the selection to limit the search scope though.



  • You read my mind. It is very specific to my project. I have been researching and I think that QDirIterator along with your class will be the solution to my problem. I will basically use QDirIterator to get all my filtered files and compare it to your model/class result and determine if it is unchecked.. Thanks!



  • Just crossed my mind, will the class you´ve implemented support multiple selection check/uncheck propagation?



  • [quote author="phamtv" date="1304006640"]You read my mind. It is very specific to my project. I have been researching and I think that QDirIterator along with your class will be the solution to my problem. I will basically use QDirIterator to get all my filtered files and compare it to your model/class result and determine if it is unchecked.. Thanks![/quote]

    I could add methods to get the unchecked files and branches like you can get the checked ones now. That would mean that you would only need to iterate over the branches returned by this method. That might be much quicker than iterating over the whole FS, and then comparing.

    [quote author="phamtv" date="1304006828"]Just crossed my mind, will the class you´ve implemented support multiple selection check/uncheck propagation?[/quote]

    I am not sure what you mean by that. Checking/unchecking all the selected files in one go?
    Such a thing is not possible to support directly, as the selection is not part of the model. The model doesn't know about which items are selected. It could be done by simply setting the check state on all indexes in a selection though.



  • bq. I could add methods to get the unchecked files and branches like you can get the checked ones now. That would mean that you would only need to iterate over the branches returned by this method. That might be much quicker than iterating over the whole FS, and then comparing.

    That would be greatly appreciated! However, like you´ve said, wouldn´t that mean that if my initial state for all the dir and files were unchecked,

    bq. That would bring you back to having to populate the entire tree and potentially recurse through the whole file system to find them all



  • [quote author="phamtv" date="1304007289"]bq. I could add methods to get the unchecked files and branches like you can get the checked ones now. That would mean that you would only need to iterate over the branches returned by this method. That might be much quicker than iterating over the whole FS, and then comparing.

    That would be greatly appreciated! However, like you´ve said, wouldn´t that mean that if my initial state for all the dir and files were unchecked,

    bq. That would bring you back to having to populate the entire tree and potentially recurse through the whole file system to find them all[/quote]

    Yes, it would. But if you are interested in unchecked items, then it would probably make sense to make checked the default for all the nodes at the start. I don't think there is a way around to recursing through at least part of the file system in your case, and a worst-case scenario for that would be recursing through the whole file system.



  • Correct! The default state would have to be checked. Can you add the methods to get unchecked files? Thanks!



  • [quote author="phamtv" date="1304007771"]Correct! The default state would have to be checked. Can you add the methods to get unchecked files? Thanks![/quote]

    Done. :-)



  • I have created a "repository":https://gitorious.org/checkableproxymodel on gitorious for this project. The licence for this project is GPL-3. Note that CheckableProxyModel makes use of my "DelayedExecutionTimer class":http://developer.qt.nokia.com/wiki/Delay_action_to_wait_for_user_interaction that I published earlier on this site (BSD).

    I have also made a separate "announcement":http://developer.qt.nokia.com/forums/viewthread/5531/ in the showcase section.



  • I sincerely appreciate your time and effort of support! This is a major piece of the puzzle for the application I am currently working on. Thank You!!!!


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.