TreeView::restoreOpennessState misbehaving?

I'm working on some file chooser code, my goal being to make a save dialog with a tree view that works the way I want. When I click "New Folder" in a save dialog I expect the new folder to be made inside of the currently selected folder, or if a file is selected, in the parent folder of that file. So, I start to modify the Juce demo, extending FileChooserDialogBox as follows:


< -> juce_FileChooserDialogBox.h >

//==============================================================================
/**
 FileChooserTreeDialogBox
 A subclass of FileChooserDialogBox to implement a tree view with folder creation in the view.
 **/

class FileChooserTreeDialogBox : public FileChooserDialogBox, public Timer
{
public:
    FileChooserTreeDialogBox (const String& title,
                          const String& instructions,
                          FileBrowserComponent& browserComponent,
                          bool warnAboutOverwritingExistingFiles,
                          Colour backgroundColour);
    
    ~FileChooserTreeDialogBox();
    
    void timerCallback();
protected:
    void createNewFolderConfirmed (const String& name);
private:
    ScopedPointer<XmlElement> savedOpennessState;
    
};

and add the implementation:


< -> juce_FileChooserDialogBox.cpp >


//==============================================================================
FileChooserTreeDialogBox::FileChooserTreeDialogBox (const String& name,
                                            const String& instructions,
                                            FileBrowserComponent& chooserComponent,
                                            const bool warnAboutOverwritingExistingFiles_,
                                            Colour backgroundColour)
: FileChooserDialogBox(name, instructions, chooserComponent, warnAboutOverwritingExistingFiles_, backgroundColour)
{
    DirectoryContentsDisplayComponent* chooserComp = content->chooserComponent.getDisplayComponent();
    FileTreeComponent* chooserAsTree = static_cast<FileTreeComponent*> (chooserComp);

    // get file list into tree view
    chooserAsTree->refresh();
    savedOpennessState = nullptr;
    startTimer(20);
}

FileChooserTreeDialogBox::~FileChooserTreeDialogBox()
{
    stopTimer();
    content->chooserComponent.removeListener (this);
}

void FileChooserTreeDialogBox::createNewFolderConfirmed (const String& nameFromDialog)
{
    const String name (File::createLegalFileName (nameFromDialog));
    if (! name.isEmpty())
    {
        const File currentFile = content->chooserComponent.getHighlightedFile();        
        const File parent = (currentFile.isDirectory()) ? currentFile : currentFile.getParentDirectory();
        
        DirectoryContentsDisplayComponent* chooserComp = content->chooserComponent.getDisplayComponent();
        
        // note that this will fail horribly if the display component is not a tree!
        // better to avoid this cast by adding to DirectoryContentsDisplayComponent.
        FileTreeComponent* chooserAsTree = static_cast<FileTreeComponent*> (chooserComp);
        
        savedOpennessState = chooserAsTree->getOpennessState (true);        
        String myXmlDoc = savedOpennessState->createDocument (String::empty);
        
        // DEBUG
        std::cout << "SAVING open state: \n"; << myXmlDoc << "\n";
        
        if (! parent.getChildFile (name).createDirectory())
        {
            AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
                                              TRANS ("New Folder"),
                                              TRANS ("Couldn't create the folder!"));
        }
        
        content->chooserComponent.refresh();
     }
}

void FileChooserTreeDialogBox::timerCallback()
{
    if(savedOpennessState != nullptr)
    {
        DirectoryContentsDisplayComponent* chooserComp = content->chooserComponent.getDisplayComponent();
        
        // note that this will fail horribly if the display component is not a tree!
        // better to avoid this cast by adding to DirectoryContentsDisplayComponent.
        FileTreeComponent* chooserAsTree = static_cast<FileTreeComponent*> (chooserComp);
        
        // DEBUG
        String myXmlDoc = savedOpennessState->createDocument (String::empty);
        std::cout << "RESTORING open state: \n" << myXmlDoc << "\n";
        
        chooserAsTree->restoreOpennessState (*savedOpennessState, false);
        savedOpennessState = nullptr;
    }
}

and also change FileChooser::showDialog to create one of these instead of an ordinary FileChooserDialogBox when the FileBrowserComponent::useTreeView flag is set. 

Now, when createNewFolderConfirmed is called, the new tree dialog looks up the currently highlighted file and uses it (or the enclosing directory) as the destination for the new folder. 

But, there's an issue when content->chooserComponent.refresh(); is called. What happens is that the whole file list is thrown away and rebuilt in DirectoryContentsList::refresh(), causing the tree view to reset and lose its current state. Bummer.

No problem though, we can use getOpennessState() to record the state of the file tree display before the refresh, and restore it afterwards. We're modal, so in order to give the file list refresh a chance to happen we first just get the openness state and yield, and then in a timer callback look for a pending state to restore. That's why the timer code is there. There is probably a cleaner way to do it. 

What happens on the openness restore, though, is not right. If I have only one subdirectory open off the root, it gets restored correctly. If I have more directories open, most of them do not get reopened properly. The XML looks fine, reflecting what directories were open before the refresh, but the file tree component is not updated properly. 

OK, I hope that explains what I'm seeing. There's either a bug with the tree view state restore or I'm missing something about how it needs to be used. Thanks in advance for any insight you can provide!

 

To make it easier to investigate this issue I've made a git patch. This patch applied to the latest tip should demonstrate the problem. After applying the patch, run the Dialogs demo and bring up the save file dialog.

 

 

 

Just FYI I have seen this, but looking at the diff, it'll take more time than I can spare right now to delve into it and see what's going on. Will try to find time when I can..