<commit_before>#include "filedialog.h"
#include "cachedfoldermodel.h"
#include "proxyfoldermodel.h"
#include "utilities.h"
#include "core/fileinfojob.h"
#include "ui_filedialog.h"

#include <QDialogButtonBox>
#include <QPushButton>
#include <QMimeType>
#include <QMimeDatabase>
#include <QMessageBox>
#include <QToolBar>
#include <QCompleter>
#include <QShortcut>
#include <QTimer>
#include <QDebug>

namespace Fm {


FileDialog::FileDialog(QWidget* parent, FilePath path) :
    QDialog(parent),
    ui{new Ui::FileDialog()},
    folderModel_{nullptr},
    proxyModel_{nullptr},
    folder_{nullptr},
    options_{0},
    viewMode_{FolderView::DetailedListMode},
    fileMode_{QFileDialog::AnyFile},
    acceptMode_{QFileDialog::AcceptOpen},
    confirmOverwrite_{true},
    modelFilter_{this} {

    ui->setupUi(this);

    // path bar
    connect(ui->location, &PathBar::chdir, [this](const FilePath &path) {
        setDirectoryPath(path);
    });

    // side pane
    ui->sidePane->setMode(Fm::SidePane::ModePlaces);
    connect(ui->sidePane, &SidePane::chdirRequested, [this](int /*type*/, const FilePath &path) {
        setDirectoryPath(path);
    });

    // folder view
    proxyModel_ = new ProxyFolderModel(this);
    proxyModel_->sort(FolderModel::ColumnFileName, Qt::AscendingOrder);
    proxyModel_->setThumbnailSize(64);
    proxyModel_->setShowThumbnails(true);

    proxyModel_->addFilter(&modelFilter_);

    connect(ui->folderView, &FolderView::clicked, this, &FileDialog::onFileClicked);
    ui->folderView->setModel(proxyModel_);
    ui->folderView->setAutoSelectionDelay(0);
    // set the completer
    QCompleter* completer = new QCompleter(this);
    completer->setModel(proxyModel_);
    ui->fileName->setCompleter(completer);
    connect(completer, static_cast<void(QCompleter::*)(const QString &)>(&QCompleter::activated), [this](const QString &text) {
        ui->folderView->selectionModel()->clearSelection();
        selectFilePath(directoryPath_.child(text.toLocal8Bit().constData()));
    });
    // select typed paths if it they exist
    connect(ui->fileName, &QLineEdit::textEdited, [this](const QString& /*text*/) {
        disconnect(ui->folderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileDialog::onSelectionChanged);
        ui->folderView->selectionModel()->clearSelection();
        QStringList parsedNames = parseNames();
        for(auto& name: parsedNames) {
            selectFilePath(directoryPath_.child(name.toLocal8Bit().constData()));
        }
        updateAcceptButtonState();
        updateSaveButtonText(false);
        connect(ui->folderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileDialog::onSelectionChanged);
    });
    // update selection mode for the view
    updateSelectionMode();

    // file type
    connect(ui->fileTypeCombo, &QComboBox::currentTextChanged, [this](const QString& text) {
        selectNameFilter(text);
    });
    ui->fileTypeCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
    ui->fileTypeCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    ui->fileTypeCombo->setCurrentIndex(0);

    QShortcut* shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_H), this);
    connect(shortcut, &QShortcut::activated, [this]() {
        proxyModel_->setShowHidden(!proxyModel_->showHidden());
    });

    // setup toolbar buttons
    auto toolbar = new QToolBar(this);
    // back button
    backAction_ = toolbar->addAction(QIcon::fromTheme("go-previous"), tr("Go Back"));
    backAction_->setShortcut(QKeySequence(tr("Alt+Left", "Go Back")));
    connect(backAction_, &QAction::triggered, [this]() {
        history_.backward();
        setDirectoryPath(history_.currentPath(), FilePath(), false);
    });
    // forward button
    forwardAction_ = toolbar->addAction(QIcon::fromTheme("go-next"), tr("Go Forward"));
    forwardAction_->setShortcut(QKeySequence(tr("Alt+Right", "Go Forward")));
    connect(forwardAction_, &QAction::triggered, [this]() {
        history_.forward();
        setDirectoryPath(history_.currentPath(), FilePath(), false);
    });
    toolbar->addSeparator();
    // reload button
    auto reloadAction = toolbar->addAction(QIcon::fromTheme("view-refresh"), tr("Reload"));
    reloadAction->setShortcut(QKeySequence(tr("F5", "Reload")));
    connect(reloadAction, &QAction::triggered, [this]() {
        if(folder_ && folder_->isLoaded()) {
            QObject::disconnect(lambdaConnection_);
            auto selFiles = ui->folderView->selectedFiles();
            ui->folderView->selectionModel()->clear();
            // reselect files on reloading
            if(!selFiles.empty()
               && selFiles.size() <= 50) { // otherwise senseless and CPU-intensive
                lambdaConnection_ = QObject::connect(folder_.get(), &Fm::Folder::finishLoading, [this, selFiles]() {
                    selectFilesOnReload(selFiles);
                });
            }
            folder_->reload();
        }
    });
    // new folder button
    auto newFolderAction = toolbar->addAction(QIcon::fromTheme("folder-new"), tr("Create Folder"));
    connect(newFolderAction, &QAction::triggered, this, &FileDialog::onNewFolder);
    toolbar->addSeparator();
    // view buttons
    auto viewModeGroup = new QActionGroup(this);
    iconViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogContentsView), tr("Icon View"));
    iconViewAction_->setCheckable(true);
    connect(iconViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled);
    viewModeGroup->addAction(iconViewAction_);
    thumbnailViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogInfoView), tr("Thumbnail View"));
    thumbnailViewAction_->setCheckable(true);
    connect(thumbnailViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled);
    viewModeGroup->addAction(thumbnailViewAction_);
    compactViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogListView), tr("Compact View"));
    compactViewAction_->setCheckable(true);
    connect(compactViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled);
    viewModeGroup->addAction(compactViewAction_);
    detailedViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogDetailedView), tr("Detailed List View"));
    detailedViewAction_->setCheckable(true);
    connect(detailedViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled);
    viewModeGroup->addAction(detailedViewAction_);
    ui->toolbarLayout->addWidget(toolbar);

    setViewMode(viewMode_);

    // set the default splitter position
    setSplitterPos(200);

    // browse to the directory
    if(path.isValid()) {
        setDirectoryPath(path);
    }
    else {
        goHome();
    }

    // focus the text entry on showing the dialog
    QTimer::singleShot(0, ui->fileName, SLOT(setFocus()));
}

FileDialog::~FileDialog() {
    freeFolder();
}

int FileDialog::splitterPos() const {
    return ui->splitter->sizes().at(0);
}

void FileDialog::setSplitterPos(int pos) {
    QList<int> sizes;
    sizes.append(qMax(pos, 0));
    sizes.append(320);
    ui->splitter->setSizes(sizes);
}

// This should always be used instead of getting text directly from the entry.
QStringList FileDialog::parseNames() const {
    // parse the file names from the text entry
    QStringList parsedNames;
    auto fileNames = ui->fileName->text();
    if(!fileNames.isEmpty()) {
        /* check if there are multiple file names (containing "),
           considering the fact that inside quotes were escaped by \ */
        auto firstQuote = fileNames.indexOf(QLatin1Char('\"'));
        auto lastQuote = fileNames.lastIndexOf(QLatin1Char('\"'));
        if(firstQuote != -1 && lastQuote != -1
           && firstQuote != lastQuote
           && (firstQuote == 0 || fileNames.at(firstQuote - 1) != QLatin1Char('\\'))
           && fileNames.at(lastQuote - 1) != QLatin1Char('\\')) {
           // split the names
            QRegExp sep{"\"\\s+\""};  // separated with " "
            parsedNames = fileNames.mid(firstQuote + 1, lastQuote - firstQuote - 1).split(sep);
            parsedNames.replaceInStrings(QLatin1String("\\\""), QLatin1String("\""));
        }
        else {
            parsedNames << fileNames.replace(QLatin1String("\\\""), QLatin1String("\""));
        }
    }
    return parsedNames;
}

std::shared_ptr<const Fm::FileInfo> FileDialog::firstSelectedDir() const {
    std::shared_ptr<const Fm::FileInfo> selectedFolder = nullptr;
    auto list = ui->folderView->selectedFiles();
    for(auto it = list.cbegin(); it != list.cend(); ++it) {
        auto& item = *it;
        if(item->isDir()) {
            selectedFolder = item;
            break;
        }
    }
    return selectedFolder;
}

void FileDialog::accept() {
    // handle selected filenames
    selectedFiles_.clear();

    // if a folder is selected in file mode, chdir into it (as QFileDialog does)
    // by giving priority to the current index and, if it isn't a folder,
    // to the first selected folder
    if(fileMode_ != QFileDialog::Directory) {
        std::shared_ptr<const Fm::FileInfo> selectedFolder = nullptr;
        // check if the current index is a folder
        QItemSelectionModel* selModel = ui->folderView->selectionModel();
        QModelIndex cur = selModel->currentIndex();
        if(cur.isValid() && selModel->isSelected(cur)) {
            auto file = proxyModel_->fileInfoFromIndex(cur);
            if(file && file->isDir()) {
                selectedFolder = file;
            }
        }
        if(!selectedFolder) {
            selectedFolder = firstSelectedDir();
        }
        if(selectedFolder) {
            setDirectoryPath(selectedFolder->path());
            return;
        }
    }

    QStringList parsedNames = parseNames();
    if(parsedNames.isEmpty()) {
        // when selecting a dir and the name is not provided, just select current dir in the view
        if(fileMode_ == QFileDialog::Directory) {
            auto localPath = directoryPath_.localPath();
            if(localPath) {
                selectedFiles_.append(QUrl::fromLocalFile(localPath.get()));
            }
            else {
                selectedFiles_.append(directory());
            }
        }
        else {
            QMessageBox::critical(this, tr("Error"), tr("Please select a file"));
            return;
        }
    }
    else {
        if(fileMode_ != QFileDialog::Directory) {
            auto firstName = parsedNames.at(0);
            auto childPath = directoryPath_.child(firstName.toLocal8Bit().constData());
            auto info = proxyModel_->fileInfoFromPath(childPath);
            if(info) {
                // if the typed name belongs to a (nonselected) directory, chdir into it
                if(info->isDir()) {
                    setDirectoryPath(childPath);
                    return;
                }
                // overwrite prompt (as in QFileDialog::accept)
                if(fileMode_ == QFileDialog::AnyFile
                   && acceptMode_ != QFileDialog::AcceptOpen
                   && confirmOverwrite_) {
                       if (QMessageBox::warning(this, windowTitle(),
                                                tr("%1 already exists.\nDo you want to replace it?")
                                                .arg(firstName),
                                                QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
                               == QMessageBox::No) {
                        return;
                    }
                }
            }
        }

        // get full paths for the filenames and convert them to URLs
        for(auto& name: parsedNames) {
            // add default filename extension as needed
            if(!defaultSuffix_.isEmpty() && name.lastIndexOf('.') == -1) {
                name += '.';
                name += defaultSuffix_;
            }
            auto fullPath = directoryPath_.child(name.toLocal8Bit().constData());
            auto localPath = fullPath.localPath();
            /* add the local path if it exists; otherwise, add the uri */
            if(localPath) {
                selectedFiles_.append(QUrl::fromLocalFile(localPath.get()));
            }
            else {
                selectedFiles_.append(QUrl::fromEncoded(fullPath.uri().get()));
            }
        }
    }

    // check existence of the selected files and if their types are correct
    // async operation, call doAccept() in the callback.
    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);

    auto pathList = pathListFromQUrls(selectedFiles_);
    auto job = new FileInfoJob(pathList);
    job->setAutoDelete(true);
    connect(job, &Job::finished, this, &FileDialog::onFileInfoJobFinished);
    job->runAsync();
}

void FileDialog::reject() {
    QDialog::reject();
}

void FileDialog::setDirectory(const QUrl &directory) {
    auto path = Fm::FilePath::fromUri(directory.toEncoded().constData());
    setDirectoryPath(path);
}

// interface for QPlatformFileDialogHelper

void FileDialog::freeFolder() {
    if(folder_) {
        QObject::disconnect(lambdaConnection_); // lambdaConnection_ can be invalid
        disconnect(folder_.get(), nullptr, this, nullptr);
        folder_ = nullptr;
    }
}

void FileDialog::goHome() {
    setDirectoryPath(FilePath::homeDir());
}

void FileDialog::setDirectoryPath(FilePath directory, FilePath selectedPath, bool addHistory) {
    if(!directory.isValid()) {
        updateAcceptButtonState(); // FIXME: is this needed?
        return;
    }

   if(directoryPath_ != directory) {
       if(folder_) {
            if(folderModel_) {
                proxyModel_->setSourceModel(nullptr);
                folderModel_->unref(); // unref the cached model
                folderModel_ = nullptr;
            }
            freeFolder();
       }
    
        directoryPath_ = std::move(directory);
    
        ui->location->setPath(directoryPath_);
        ui->sidePane->chdir(directoryPath_);
        if(addHistory) {
            history_.add(directoryPath_);
        }
        backAction_->setEnabled(history_.canBackward());
        forwardAction_->setEnabled(history_.canForward());
    
        folder_ = Fm::Folder::fromPath(directoryPath_);
        folderModel_ = CachedFolderModel::modelFromFolder(folder_);
        proxyModel_->setSourceModel(folderModel_);
    
        // no lambda in these connections for easy disconnection
        connect(folder_.get(), &Fm::Folder::removed, this, &FileDialog::goHome);
        connect(folder_.get(), &Fm::Folder::unmount, this, &FileDialog::goHome);
    
        QUrl uri = QUrl::fromEncoded(directory.uri().get());
        Q_EMIT directoryEntered(uri);
   }

    // select the path if valid
    if(selectedPath.isValid()) {
        if(folder_->isLoaded()) {
            selectFilePathWithDelay(selectedPath);
        }
        else {
            lambdaConnection_ = QObject::connect(folder_.get(), &Fm::Folder::finishLoading, [this, selectedPath]() {
                selectFilePathWithDelay(selectedPath);
            });
        }
    }
    else {
        updateAcceptButtonState();
        updateSaveButtonText(false);
    }

}

void FileDialog::selectFilePath(const FilePath &path) {
    auto idx = proxyModel_->indexFromPath(path);
    if(!idx.isValid()) {
        return;
    }

    // FIXME: add a method to Fm::FolderView to select files

    // FIXME: need to add this for detailed list
    QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Select;
    if(viewMode_ == FolderView::DetailedListMode) {
        flags |= QItemSelectionModel::Rows;
    }
    QItemSelectionModel* selModel = ui->folderView->selectionModel();
    selModel->select(idx, flags);
    selModel->setCurrentIndex(idx, QItemSelectionModel::Current);
    QTimer::singleShot(0, [this, idx]() {
        ui->folderView->childView()->scrollTo(idx, QAbstractItemView::PositionAtCenter);
    });
}

void FileDialog::selectFilePathWithDelay(const FilePath &path) {
    QTimer::singleShot(0, [this, path]() {
        if(acceptMode_ == QFileDialog::AcceptSave) {
            // with a save dialog, always put the base name in line-edit, regardless of selection
            ui->fileName->setText(path.baseName().get());
        }
        // update "accept" button because there might be no selection later
        updateAcceptButtonState();
        updateSaveButtonText(false);
        // try to select path
        selectFilePath(path);
    });
}

void FileDialog::selectFilesOnReload(const Fm::FileInfoList& infos) {
    QObject::disconnect(lambdaConnection_);
    QTimer::singleShot(0, [this, infos]() {
        for(auto& fileInfo: infos) {
            selectFilePath(fileInfo->path());
        }
    });
}

void FileDialog::onCurrentRowChanged(const QModelIndex &current, const QModelIndex& /*previous*/) {
    // emit currentChanged signal
    QUrl currentUrl;
    if(current.isValid()) {
        // emit changed siangl for newly selected items
        auto fi = proxyModel_->fileInfoFromIndex(current);
        if(fi) {
            currentUrl = QUrl::fromEncoded(fi->path().uri().get());
        }
    }
    Q_EMIT currentChanged(currentUrl);
}

void FileDialog::onSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) {
    auto selFiles = ui->folderView->selectedFiles();
    if(selFiles.empty()) {
        updateAcceptButtonState();
        updateSaveButtonText(false);
        return;
    }<commit_msg>Fix lambda connections in filedialog (#159)<commit_after>#include "filedialog.h"
#include "cachedfoldermodel.h"
#include "proxyfoldermodel.h"
#include "utilities.h"
#include "core/fileinfojob.h"
#include "ui_filedialog.h"

#include <QDialogButtonBox>
#include <QPushButton>
#include <QMimeType>
#include <QMimeDatabase>
#include <QMessageBox>
#include <QToolBar>
#include <QCompleter>
#include <QShortcut>
#include <QTimer>
#include <QDebug>

namespace Fm {


FileDialog::FileDialog(QWidget* parent, FilePath path) :
    QDialog(parent),
    ui{new Ui::FileDialog()},
    folderModel_{nullptr},
    proxyModel_{nullptr},
    folder_{nullptr},
    options_{0},
    viewMode_{FolderView::DetailedListMode},
    fileMode_{QFileDialog::AnyFile},
    acceptMode_{QFileDialog::AcceptOpen},
    confirmOverwrite_{true},
    modelFilter_{this} {

    ui->setupUi(this);

    // path bar
    connect(ui->location, &PathBar::chdir, [this](const FilePath &path) {
        setDirectoryPath(path);
    });

    // side pane
    ui->sidePane->setMode(Fm::SidePane::ModePlaces);
    connect(ui->sidePane, &SidePane::chdirRequested, [this](int /*type*/, const FilePath &path) {
        setDirectoryPath(path);
    });

    // folder view
    proxyModel_ = new ProxyFolderModel(this);
    proxyModel_->sort(FolderModel::ColumnFileName, Qt::AscendingOrder);
    proxyModel_->setThumbnailSize(64);
    proxyModel_->setShowThumbnails(true);

    proxyModel_->addFilter(&modelFilter_);

    connect(ui->folderView, &FolderView::clicked, this, &FileDialog::onFileClicked);
    ui->folderView->setModel(proxyModel_);
    ui->folderView->setAutoSelectionDelay(0);
    // set the completer
    QCompleter* completer = new QCompleter(this);
    completer->setModel(proxyModel_);
    ui->fileName->setCompleter(completer);
    connect(completer, static_cast<void(QCompleter::*)(const QString &)>(&QCompleter::activated), [this](const QString &text) {
        ui->folderView->selectionModel()->clearSelection();
        selectFilePath(directoryPath_.child(text.toLocal8Bit().constData()));
    });
    // select typed paths if it they exist
    connect(ui->fileName, &QLineEdit::textEdited, [this](const QString& /*text*/) {
        disconnect(ui->folderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileDialog::onSelectionChanged);
        ui->folderView->selectionModel()->clearSelection();
        QStringList parsedNames = parseNames();
        for(auto& name: parsedNames) {
            selectFilePath(directoryPath_.child(name.toLocal8Bit().constData()));
        }
        updateAcceptButtonState();
        updateSaveButtonText(false);
        connect(ui->folderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileDialog::onSelectionChanged);
    });
    // update selection mode for the view
    updateSelectionMode();

    // file type
    connect(ui->fileTypeCombo, &QComboBox::currentTextChanged, [this](const QString& text) {
        selectNameFilter(text);
    });
    ui->fileTypeCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
    ui->fileTypeCombo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    ui->fileTypeCombo->setCurrentIndex(0);

    QShortcut* shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_H), this);
    connect(shortcut, &QShortcut::activated, [this]() {
        proxyModel_->setShowHidden(!proxyModel_->showHidden());
    });

    // setup toolbar buttons
    auto toolbar = new QToolBar(this);
    // back button
    backAction_ = toolbar->addAction(QIcon::fromTheme("go-previous"), tr("Go Back"));
    backAction_->setShortcut(QKeySequence(tr("Alt+Left", "Go Back")));
    connect(backAction_, &QAction::triggered, [this]() {
        history_.backward();
        setDirectoryPath(history_.currentPath(), FilePath(), false);
    });
    // forward button
    forwardAction_ = toolbar->addAction(QIcon::fromTheme("go-next"), tr("Go Forward"));
    forwardAction_->setShortcut(QKeySequence(tr("Alt+Right", "Go Forward")));
    connect(forwardAction_, &QAction::triggered, [this]() {
        history_.forward();
        setDirectoryPath(history_.currentPath(), FilePath(), false);
    });
    toolbar->addSeparator();
    // reload button
    auto reloadAction = toolbar->addAction(QIcon::fromTheme("view-refresh"), tr("Reload"));
    reloadAction->setShortcut(QKeySequence(tr("F5", "Reload")));
    connect(reloadAction, &QAction::triggered, [this]() {
        if(folder_ && folder_->isLoaded()) {
            QObject::disconnect(lambdaConnection_);
            auto selFiles = ui->folderView->selectedFiles();
            ui->folderView->selectionModel()->clear();
            // reselect files on reloading
            if(!selFiles.empty()
               && selFiles.size() <= 50) { // otherwise senseless and CPU-intensive
                lambdaConnection_ = QObject::connect(folder_.get(), &Fm::Folder::finishLoading, [this, selFiles]() {
                    selectFilesOnReload(selFiles);
                });
            }
            folder_->reload();
        }
    });
    // new folder button
    auto newFolderAction = toolbar->addAction(QIcon::fromTheme("folder-new"), tr("Create Folder"));
    connect(newFolderAction, &QAction::triggered, this, &FileDialog::onNewFolder);
    toolbar->addSeparator();
    // view buttons
    auto viewModeGroup = new QActionGroup(this);
    iconViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogContentsView), tr("Icon View"));
    iconViewAction_->setCheckable(true);
    connect(iconViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled);
    viewModeGroup->addAction(iconViewAction_);
    thumbnailViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogInfoView), tr("Thumbnail View"));
    thumbnailViewAction_->setCheckable(true);
    connect(thumbnailViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled);
    viewModeGroup->addAction(thumbnailViewAction_);
    compactViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogListView), tr("Compact View"));
    compactViewAction_->setCheckable(true);
    connect(compactViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled);
    viewModeGroup->addAction(compactViewAction_);
    detailedViewAction_ = toolbar->addAction(style()->standardIcon(QStyle::SP_FileDialogDetailedView), tr("Detailed List View"));
    detailedViewAction_->setCheckable(true);
    connect(detailedViewAction_, &QAction::toggled, this, &FileDialog::onViewModeToggled);
    viewModeGroup->addAction(detailedViewAction_);
    ui->toolbarLayout->addWidget(toolbar);

    setViewMode(viewMode_);

    // set the default splitter position
    setSplitterPos(200);

    // browse to the directory
    if(path.isValid()) {
        setDirectoryPath(path);
    }
    else {
        goHome();
    }

    // focus the text entry on showing the dialog
    QTimer::singleShot(0, ui->fileName, SLOT(setFocus()));
}

FileDialog::~FileDialog() {
    freeFolder();
}

int FileDialog::splitterPos() const {
    return ui->splitter->sizes().at(0);
}

void FileDialog::setSplitterPos(int pos) {
    QList<int> sizes;
    sizes.append(qMax(pos, 0));
    sizes.append(320);
    ui->splitter->setSizes(sizes);
}

// This should always be used instead of getting text directly from the entry.
QStringList FileDialog::parseNames() const {
    // parse the file names from the text entry
    QStringList parsedNames;
    auto fileNames = ui->fileName->text();
    if(!fileNames.isEmpty()) {
        /* check if there are multiple file names (containing "),
           considering the fact that inside quotes were escaped by \ */
        auto firstQuote = fileNames.indexOf(QLatin1Char('\"'));
        auto lastQuote = fileNames.lastIndexOf(QLatin1Char('\"'));
        if(firstQuote != -1 && lastQuote != -1
           && firstQuote != lastQuote
           && (firstQuote == 0 || fileNames.at(firstQuote - 1) != QLatin1Char('\\'))
           && fileNames.at(lastQuote - 1) != QLatin1Char('\\')) {
           // split the names
            QRegExp sep{"\"\\s+\""};  // separated with " "
            parsedNames = fileNames.mid(firstQuote + 1, lastQuote - firstQuote - 1).split(sep);
            parsedNames.replaceInStrings(QLatin1String("\\\""), QLatin1String("\""));
        }
        else {
            parsedNames << fileNames.replace(QLatin1String("\\\""), QLatin1String("\""));
        }
    }
    return parsedNames;
}

std::shared_ptr<const Fm::FileInfo> FileDialog::firstSelectedDir() const {
    std::shared_ptr<const Fm::FileInfo> selectedFolder = nullptr;
    auto list = ui->folderView->selectedFiles();
    for(auto it = list.cbegin(); it != list.cend(); ++it) {
        auto& item = *it;
        if(item->isDir()) {
            selectedFolder = item;
            break;
        }
    }
    return selectedFolder;
}

void FileDialog::accept() {
    // handle selected filenames
    selectedFiles_.clear();

    // if a folder is selected in file mode, chdir into it (as QFileDialog does)
    // by giving priority to the current index and, if it isn't a folder,
    // to the first selected folder
    if(fileMode_ != QFileDialog::Directory) {
        std::shared_ptr<const Fm::FileInfo> selectedFolder = nullptr;
        // check if the current index is a folder
        QItemSelectionModel* selModel = ui->folderView->selectionModel();
        QModelIndex cur = selModel->currentIndex();
        if(cur.isValid() && selModel->isSelected(cur)) {
            auto file = proxyModel_->fileInfoFromIndex(cur);
            if(file && file->isDir()) {
                selectedFolder = file;
            }
        }
        if(!selectedFolder) {
            selectedFolder = firstSelectedDir();
        }
        if(selectedFolder) {
            setDirectoryPath(selectedFolder->path());
            return;
        }
    }

    QStringList parsedNames = parseNames();
    if(parsedNames.isEmpty()) {
        // when selecting a dir and the name is not provided, just select current dir in the view
        if(fileMode_ == QFileDialog::Directory) {
            auto localPath = directoryPath_.localPath();
            if(localPath) {
                selectedFiles_.append(QUrl::fromLocalFile(localPath.get()));
            }
            else {
                selectedFiles_.append(directory());
            }
        }
        else {
            QMessageBox::critical(this, tr("Error"), tr("Please select a file"));
            return;
        }
    }
    else {
        if(fileMode_ != QFileDialog::Directory) {
            auto firstName = parsedNames.at(0);
            auto childPath = directoryPath_.child(firstName.toLocal8Bit().constData());
            auto info = proxyModel_->fileInfoFromPath(childPath);
            if(info) {
                // if the typed name belongs to a (nonselected) directory, chdir into it
                if(info->isDir()) {
                    setDirectoryPath(childPath);
                    return;
                }
                // overwrite prompt (as in QFileDialog::accept)
                if(fileMode_ == QFileDialog::AnyFile
                   && acceptMode_ != QFileDialog::AcceptOpen
                   && confirmOverwrite_) {
                       if (QMessageBox::warning(this, windowTitle(),
                                                tr("%1 already exists.\nDo you want to replace it?")
                                                .arg(firstName),
                                                QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
                               == QMessageBox::No) {
                        return;
                    }
                }
            }
        }

        // get full paths for the filenames and convert them to URLs
        for(auto& name: parsedNames) {
            // add default filename extension as needed
            if(!defaultSuffix_.isEmpty() && name.lastIndexOf('.') == -1) {
                name += '.';
                name += defaultSuffix_;
            }
            auto fullPath = directoryPath_.child(name.toLocal8Bit().constData());
            auto localPath = fullPath.localPath();
            /* add the local path if it exists; otherwise, add the uri */
            if(localPath) {
                selectedFiles_.append(QUrl::fromLocalFile(localPath.get()));
            }
            else {
                selectedFiles_.append(QUrl::fromEncoded(fullPath.uri().get()));
            }
        }
    }

    // check existence of the selected files and if their types are correct
    // async operation, call doAccept() in the callback.
    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);

    auto pathList = pathListFromQUrls(selectedFiles_);
    auto job = new FileInfoJob(pathList);
    job->setAutoDelete(true);
    connect(job, &Job::finished, this, &FileDialog::onFileInfoJobFinished);
    job->runAsync();
}

void FileDialog::reject() {
    QDialog::reject();
}

void FileDialog::setDirectory(const QUrl &directory) {
    auto path = Fm::FilePath::fromUri(directory.toEncoded().constData());
    setDirectoryPath(path);
}

// interface for QPlatformFileDialogHelper

void FileDialog::freeFolder() {
    if(folder_) {
        QObject::disconnect(lambdaConnection_); // lambdaConnection_ can be invalid
        disconnect(folder_.get(), nullptr, this, nullptr);
        folder_ = nullptr;
    }
}

void FileDialog::goHome() {
    setDirectoryPath(FilePath::homeDir());
}

void FileDialog::setDirectoryPath(FilePath directory, FilePath selectedPath, bool addHistory) {
    if(!directory.isValid()) {
        updateAcceptButtonState(); // FIXME: is this needed?
        return;
    }

   if(directoryPath_ != directory) {
       if(folder_) {
            if(folderModel_) {
                proxyModel_->setSourceModel(nullptr);
                folderModel_->unref(); // unref the cached model
                folderModel_ = nullptr;
            }
            freeFolder();
       }
    
        directoryPath_ = std::move(directory);
    
        ui->location->setPath(directoryPath_);
        ui->sidePane->chdir(directoryPath_);
        if(addHistory) {
            history_.add(directoryPath_);
        }
        backAction_->setEnabled(history_.canBackward());
        forwardAction_->setEnabled(history_.canForward());
    
        folder_ = Fm::Folder::fromPath(directoryPath_);
        folderModel_ = CachedFolderModel::modelFromFolder(folder_);
        proxyModel_->setSourceModel(folderModel_);
    
        // no lambda in these connections for easy disconnection
        connect(folder_.get(), &Fm::Folder::removed, this, &FileDialog::goHome);
        connect(folder_.get(), &Fm::Folder::unmount, this, &FileDialog::goHome);
    
        QUrl uri = QUrl::fromEncoded(directory.uri().get());
        Q_EMIT directoryEntered(uri);
   }

    // select the path if valid
    if(selectedPath.isValid()) {
        if(folder_->isLoaded()) {
            selectFilePathWithDelay(selectedPath);
        }
        else {
            lambdaConnection_ = QObject::connect(folder_.get(), &Fm::Folder::finishLoading, [this, selectedPath]() {
                selectFilePathWithDelay(selectedPath);
            });
        }
    }
    else {
        updateAcceptButtonState();
        updateSaveButtonText(false);
    }

}

void FileDialog::selectFilePath(const FilePath &path) {
    auto idx = proxyModel_->indexFromPath(path);
    if(!idx.isValid()) {
        return;
    }

    // FIXME: add a method to Fm::FolderView to select files

    // FIXME: need to add this for detailed list
    QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Select;
    if(viewMode_ == FolderView::DetailedListMode) {
        flags |= QItemSelectionModel::Rows;
    }
    QItemSelectionModel* selModel = ui->folderView->selectionModel();
    selModel->select(idx, flags);
    selModel->setCurrentIndex(idx, QItemSelectionModel::Current);
    QTimer::singleShot(0, this, [this, idx]() {
        ui->folderView->childView()->scrollTo(idx, QAbstractItemView::PositionAtCenter);
    });
}

void FileDialog::selectFilePathWithDelay(const FilePath &path) {
    QTimer::singleShot(0, this, [this, path]() {
        if(acceptMode_ == QFileDialog::AcceptSave) {
            // with a save dialog, always put the base name in line-edit, regardless of selection
            ui->fileName->setText(path.baseName().get());
        }
        // update "accept" button because there might be no selection later
        updateAcceptButtonState();
        updateSaveButtonText(false);
        // try to select path
        selectFilePath(path);
    });
}

void FileDialog::selectFilesOnReload(const Fm::FileInfoList& infos) {
    QObject::disconnect(lambdaConnection_);
    QTimer::singleShot(0, this, [this, infos]() {
        for(auto& fileInfo: infos) {
            selectFilePath(fileInfo->path());
        }
    });
}

void FileDialog::onCurrentRowChanged(const QModelIndex &current, const QModelIndex& /*previous*/) {
    // emit currentChanged signal
    QUrl currentUrl;
    if(current.isValid()) {
        // emit changed siangl for newly selected items
        auto fi = proxyModel_->fileInfoFromIndex(current);
        if(fi) {
            currentUrl = QUrl::fromEncoded(fi->path().uri().get());
        }
    }
    Q_EMIT currentChanged(currentUrl);
}

void FileDialog::onSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) {
    auto selFiles = ui->folderView->selectedFiles();
    if(selFiles.empty()) {
        updateAcceptButtonState();
        updateSaveButtonText(false);
        return;
    }
{'fraction_non_alphanumeric': 0.1329286281629411, 'fraction_numerical': 0.0024679450811301486, 'mean_word_length': 2.8966508234773958, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 273, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2180159', 'n_tokens_mistral': 9940, 'n_tokens_neox': 9774, 'n_words': 1912}