Bug? Non-native FileChooser with TreeView takes 5 seconds to open Save dialog

JUCE 7.0.0. MacOS.

I’m just experimenting with getting a FileBrowser working. I’m using a simple example taken from the docs basically. I’m not doing anything with the results; I just want to get the FileBrowser to come up.

When using the non-native option (JUCE style), and specifying useTreeView, it takes like 5 seconds to open the Save dialog, but the Open dialog with the same code opens nearly instantly.

I am specifying my home folder as the root, which contains a lot of files and directories (I suppose), but why does the Open dialog open nearly immediately, while the Save version takes 5 seconds to open?

When NOT using the TreeView option, they both open immediately.

Untitled4

Here is a portion of the relevant code:

    std::unique_ptr<juce::FileChooser> myChooser;
    bool useNative = false;
    bool useTreeView = false;

void MainComponent::openFile()
{
    myChooser.reset (new FileChooser ("Please select the file you want to load...",
                                               File::getSpecialLocation (File::userHomeDirectory),
                                               "*", useNative));
    
    auto folderChooserFlags = FileBrowserComponent::openMode
                            | (useTreeView ? FileBrowserComponent::useTreeView : 0)
                            | FileBrowserComponent::canSelectFiles;
    
    myChooser->launchAsync (folderChooserFlags, [this] (const FileChooser& chooser)
                            {
                                File theFile (chooser.getResult());
                                
                                if (theFile.exists())
                                    DBG("GOOD!" + theFile.getFullPathName());
                                else
                                {
                                    DBG("CANCELLED!");
                                    return;
                                }
                            });
}

void MainComponent::saveFile()
{
    myChooser.reset (new FileChooser ("Choose a file to save...",
                                       File::getSpecialLocation (File::userHomeDirectory).getChildFile ( "default.txt" ),
                                        "*", useNative));
    
    auto folderChooserFlags = FileBrowserComponent::saveMode
                            | (useTreeView ? FileBrowserComponent::useTreeView : 0)
                            | FileBrowserComponent::canSelectFiles;
    
    myChooser->launchAsync (folderChooserFlags, [this] (const FileChooser& chooser)
                            {
                                auto result = chooser.getURLResult();   // will be empty if cancelled
                                
                                if (result.isEmpty())
                                    return;                            
                            });
}

Here is the example project. I really would like to use the TreeView, but 5 seconds to open a dialog - something is wrong.

FileChooserTest.zip (12.2 KB)

Is it possible for someone from the JUCE team to please take a quick look at this?

I know you’re probably really busy, but the sample project is right here and I’d love to know if you can replicate my results, or what I’m doing wrong. I’m really stuck on this.

I ran the compiled .app example on my other Mac (BigSur), completely different computer and directory contents, it did the exact same thing. Non-native TreeView dialog for Opening files opens immediately; for Saving files, takes 5 seconds to open - with the same starting directory specified. Thanks!

FWIW, I can reproduce the behaviour that you are seeing also on my machine, a macMini with M1 chip and macOS 12.4, latest JUCE develop (commit 988d65e2, past the 7.0.2 version tag).

I thought that the number of files to be scanned for displaying the TreeView could be a factor, but I was wrong: the non-native Save dialog with TreeView takes 5 seconds to appear even when pointed to a directory that only contains one file and no subdirectories.

Unfortunalely, I can’t debug this further but you could perhaps run a profiling session to see where that time is spent.

1 Like

Thanks for confirming. I managed to find where the time is being spent, this seems like a bug.

In juce_FileTreeComponent.cpp, there is a class FileListTreeItem. So this is FileTreeListItem::selectFile():

    bool selectFile (const File& target)
    {
        if (file == target)
        {
            setSelected (true, true);
            return true;
        }

        if (target.isAChildOf (file))
        {
            setOpen (true);

            for (int maxRetries = 500; --maxRetries > 0;)
            {
                for (int i = 0; i < getNumSubItems(); ++i)
                    if (auto* f = dynamic_cast<FileListTreeItem*> (getSubItem (i)))
                        if (f->selectFile (target))
                            return true;

                // if we've just opened and the contents are still loading, wait for it..
                if (subContentsList != nullptr && subContentsList->isStillLoading())
                {
                    Thread::sleep (10);
                    rebuildItemsFromContentList();
                }
                else
                {
                    break;
                }
            }
        }

        return false;
    }

Above, you can see a loop that runs 500 times and sleeps each time for 10 ms (so that’s 5 seconds).

This function is called as a result of specifying the default file name to be saved. When the browser is launched, in FileBrowserComponent() constructor:

    if (initialFileOrDirectory == File())
    {
        currentRoot = File::getCurrentWorkingDirectory();
    }
    else if (initialFileOrDirectory.isDirectory())
    {
        currentRoot = initialFileOrDirectory;
    }
    else
    {
        chosenFiles.add (initialFileOrDirectory);
        currentRoot = initialFileOrDirectory.getParentDirectory();
        filename = initialFileOrDirectory.getFileName();   //< == RIGHT HERE!
    }

Then, farther down, that calls:

   if (filename.isNotEmpty())
        setFileName (filename);

Which ends up calling FileTreeListItem::selectFile() (above) where it hangs for 5 seconds.

For example, a default file name in the current working directory:

    myChooser.reset (new FileChooser ("Choose a file to save...",
                                      File::getCurrentWorkingDirectory().getChildFile ( "default.txt" ),
                                      "*", useNative));

This causes it to hang for 5 seconds.

If I specify no file name, it opens immediately:

    myChooser.reset (new FileChooser ("Choose a file to save...",
                                      File(),
                                      "*", useNative));

If I simply comment out the call to setFilename in the constructor, it seems to work perfectly fine in that the dialog comes up instantly with my default name specified.

These changes should allow the FileChooser to open instantly:

4 Likes

@reuk - it’s working correctly now. Thanks a bunch!

2 Likes