966 lines
33 KiB
C++
966 lines
33 KiB
C++
#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 ¤t, 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;
|
|
}
|
|
bool multiple(selFiles.size() > 1);
|
|
bool hasDir(false);
|
|
QString fileNames;
|
|
for(auto& fileInfo: selFiles) {
|
|
if(fileMode_ == QFileDialog::Directory) {
|
|
// if we want to select dir, ignore selected files
|
|
if(!fileInfo->isDir()) {
|
|
continue;
|
|
}
|
|
}
|
|
else if(fileInfo->isDir()) {
|
|
// if we want to select files, ignore selected dirs
|
|
hasDir = true;
|
|
continue;
|
|
}
|
|
|
|
auto baseName = fileInfo->path().baseName();
|
|
if(multiple) {
|
|
// support multiple selection
|
|
if(!fileNames.isEmpty()) {
|
|
fileNames += ' ';
|
|
}
|
|
fileNames += QLatin1Char('\"');
|
|
// escape inside quotes with \ to distinguish between them
|
|
// and the quotes used for separating file names from each other
|
|
QString name(baseName.get());
|
|
fileNames += name.replace(QLatin1String("\""), QLatin1String("\\\""));
|
|
fileNames += QLatin1Char('\"');
|
|
}
|
|
else {
|
|
// support single selection only
|
|
QString name(baseName.get());
|
|
fileNames = name.replace(QLatin1String("\""), QLatin1String("\\\""));
|
|
break;
|
|
}
|
|
}
|
|
// put the selection list in the text entry
|
|
if(!fileNames.isEmpty()) {
|
|
ui->fileName->setText(fileNames);
|
|
}
|
|
updateSaveButtonText(hasDir);
|
|
updateAcceptButtonState();
|
|
}
|
|
|
|
void FileDialog::onFileClicked(int type, const std::shared_ptr<const FileInfo> &file) {
|
|
bool canAccept = false;
|
|
if(file && type == FolderView::ActivatedClick) {
|
|
if(file->isDir()) {
|
|
if(fileMode_ == QFileDialog::Directory) {
|
|
ui->fileName->clear();
|
|
}
|
|
// chdir into the activated dir
|
|
setDirectoryPath(file->path());
|
|
}
|
|
else if(fileMode_ != QFileDialog::Directory) {
|
|
// select file(s) and a file item is activated
|
|
canAccept = true;
|
|
}
|
|
}
|
|
|
|
if(canAccept) {
|
|
selectFilePath(file->path());
|
|
accept();
|
|
}
|
|
}
|
|
|
|
void FileDialog::onNewFolder() {
|
|
createFileOrFolder(CreateNewFolder, directoryPath_, nullptr, this);
|
|
}
|
|
|
|
void FileDialog::onViewModeToggled(bool active) {
|
|
if(active) {
|
|
auto action = static_cast<QAction*>(sender());
|
|
FolderView::ViewMode newMode;
|
|
if(action == iconViewAction_) {
|
|
newMode = FolderView::IconMode;
|
|
}
|
|
else if(action == thumbnailViewAction_) {
|
|
newMode = FolderView::ThumbnailMode;
|
|
}
|
|
else if(action == compactViewAction_) {
|
|
newMode = FolderView::CompactMode;
|
|
}
|
|
else if(action == detailedViewAction_) {
|
|
newMode = FolderView::DetailedListMode;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
setViewMode(newMode);
|
|
}
|
|
}
|
|
|
|
void FileDialog::updateSelectionMode() {
|
|
// enable multiple selection?
|
|
ui->folderView->childView()->setSelectionMode(fileMode_ == QFileDialog::ExistingFiles ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection);
|
|
}
|
|
|
|
void FileDialog::doAccept() {
|
|
|
|
Q_EMIT filesSelected(selectedFiles_);
|
|
|
|
if(selectedFiles_.size() == 1) {
|
|
Q_EMIT fileSelected(selectedFiles_[0]);
|
|
}
|
|
|
|
QDialog::accept();
|
|
}
|
|
|
|
void FileDialog::onFileInfoJobFinished() {
|
|
auto job = static_cast<FileInfoJob*>(sender());
|
|
if(job->isCancelled()) {
|
|
selectedFiles_.clear();
|
|
reject();
|
|
}
|
|
else {
|
|
QString error;
|
|
// check if the files exist and their types are correct
|
|
auto paths = job->paths();
|
|
auto files = job->files();
|
|
for(size_t i = 0; i < paths.size(); ++i) {
|
|
const auto& path = paths[i];
|
|
if(i >= files.size() || files[i]->path() != path) {
|
|
// the file path is not found and does not have file info
|
|
if(fileMode_ != QFileDialog::AnyFile) {
|
|
// if we do not allow non-existent file, this is an error.
|
|
error = tr("Path \"%1\" does not exist").arg(path.displayName().get());
|
|
break;
|
|
}
|
|
++i; // skip the file
|
|
continue;
|
|
}
|
|
|
|
// FIXME: currently, if a path is not found, FmFileInfoJob does not return its file info object.
|
|
// This is bad API design. We may return nullptr for the failed file info query instead.
|
|
const auto& file = files[i];
|
|
// check if the file type is correct
|
|
if(fileMode_ == QFileDialog::Directory) {
|
|
if(!file->isDir()) {
|
|
// we're selecting dirs, but the selected file path does not point to a dir
|
|
error = tr("\"%1\" is not a directory").arg(path.displayName().get());
|
|
break;
|
|
}
|
|
}
|
|
else if(file->isDir() || file->isShortcut()) {
|
|
// we're selecting files, but the selected file path refers to a dir or shortcut (such as computer:///)
|
|
error = tr("\"%1\" is not a file").arg(path.displayName().get());;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(error.isEmpty()) {
|
|
// no error!
|
|
doAccept();
|
|
}
|
|
else {
|
|
QMessageBox::critical(this, tr("Error"), error);
|
|
selectedFiles_.clear();
|
|
}
|
|
}
|
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
|
}
|
|
|
|
QUrl FileDialog::directory() const {
|
|
QUrl url{directoryPath_.uri().get()};
|
|
return url;
|
|
}
|
|
|
|
void FileDialog::selectFile(const QUrl& filename) {
|
|
auto urlStr = filename.toEncoded();
|
|
auto path = FilePath::fromUri(urlStr.constData());
|
|
auto parent = path.parent();
|
|
// chdir into file's parent if needed and select the file
|
|
setDirectoryPath(parent, path);
|
|
}
|
|
|
|
QList<QUrl> FileDialog::selectedFiles() {
|
|
return selectedFiles_;
|
|
}
|
|
|
|
void FileDialog::selectNameFilter(const QString& filter) {
|
|
if(filter != currentNameFilter_) {
|
|
currentNameFilter_ = filter;
|
|
ui->fileTypeCombo->setCurrentText(filter);
|
|
|
|
modelFilter_.update();
|
|
proxyModel_->invalidate();
|
|
Q_EMIT filterSelected(filter);
|
|
}
|
|
}
|
|
|
|
void FileDialog::selectMimeTypeFilter(const QString &filter) {
|
|
auto idx = mimeTypeFilters_.indexOf(filter);
|
|
if(idx != -1) {
|
|
ui->fileTypeCombo->setCurrentIndex(idx);
|
|
}
|
|
}
|
|
|
|
QString FileDialog::selectedMimeTypeFilter() const {
|
|
QString filter;
|
|
auto idx = mimeTypeFilters_.indexOf(filter);
|
|
if(idx >= 0 && idx < mimeTypeFilters_.size()) {
|
|
filter = mimeTypeFilters_[idx];
|
|
}
|
|
return filter;
|
|
}
|
|
|
|
bool FileDialog::isSupportedUrl(const QUrl& url) {
|
|
auto scheme = url.scheme().toLocal8Bit();
|
|
// FIXME: this is not reliable due to the bug of gvfs.
|
|
return Fm::isUriSchemeSupported(scheme.constData());
|
|
}
|
|
|
|
|
|
// options
|
|
|
|
void FileDialog::setFilter(QDir::Filters filters) {
|
|
filters_ = filters;
|
|
// TODO:
|
|
}
|
|
|
|
void FileDialog::setViewMode(FolderView::ViewMode mode) {
|
|
viewMode_ = mode;
|
|
|
|
// Since setModel() is called by FolderView::setViewMode(), the selectionModel will be replaced by one
|
|
// created by the view. So, we need to deal with selection changes again after setting the view mode.
|
|
disconnect(ui->folderView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &FileDialog::onCurrentRowChanged);
|
|
disconnect(ui->folderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileDialog::onSelectionChanged);
|
|
|
|
ui->folderView->setViewMode(mode);
|
|
switch(mode) {
|
|
case FolderView::IconMode:
|
|
iconViewAction_->setChecked(true);
|
|
break;
|
|
case FolderView::ThumbnailMode:
|
|
thumbnailViewAction_->setChecked(true);
|
|
break;
|
|
case FolderView::CompactMode:
|
|
compactViewAction_->setChecked(true);
|
|
break;
|
|
case FolderView::DetailedListMode:
|
|
detailedViewAction_->setChecked(true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
// selection changes
|
|
connect(ui->folderView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &FileDialog::onCurrentRowChanged);
|
|
connect(ui->folderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &FileDialog::onSelectionChanged);
|
|
// update selection mode for the view
|
|
updateSelectionMode();
|
|
}
|
|
|
|
|
|
void FileDialog::setFileMode(QFileDialog::FileMode mode) {
|
|
if(mode == QFileDialog::DirectoryOnly) {
|
|
// directly only is deprecated and not allowed.
|
|
mode = QFileDialog::Directory;
|
|
}
|
|
fileMode_ = mode;
|
|
|
|
// enable multiple selection?
|
|
updateSelectionMode();
|
|
}
|
|
|
|
|
|
void FileDialog::setAcceptMode(QFileDialog::AcceptMode mode) {
|
|
acceptMode_ = mode;
|
|
// set "open/save" label if it isn't set explicitly
|
|
if(isLabelExplicitlySet(QFileDialog::Accept)) {
|
|
return;
|
|
}
|
|
if(acceptMode_ == QFileDialog::AcceptOpen) {
|
|
setLabelTextControl(QFileDialog::Accept, tr("&Open"));
|
|
}
|
|
else if(acceptMode_ == QFileDialog::AcceptSave) {
|
|
setLabelTextControl(QFileDialog::Accept, tr("&Save"));
|
|
}
|
|
}
|
|
|
|
void FileDialog::setNameFilters(const QStringList& filters) {
|
|
if(filters.isEmpty()) {
|
|
// default filename pattern
|
|
nameFilters_ = (QStringList() << tr("All Files (*)"));
|
|
}
|
|
else {
|
|
nameFilters_ = filters;
|
|
}
|
|
ui->fileTypeCombo->clear();
|
|
ui->fileTypeCombo->addItems(nameFilters_);
|
|
}
|
|
|
|
void FileDialog::setMimeTypeFilters(const QStringList& filters) {
|
|
mimeTypeFilters_ = filters;
|
|
|
|
QStringList nameFilters;
|
|
QMimeDatabase db;
|
|
for(const auto& filter: filters) {
|
|
auto mimeType = db.mimeTypeForName(filter);
|
|
auto nameFilter = mimeType.comment();
|
|
if(!mimeType.suffixes().empty()) {
|
|
nameFilter + " (";
|
|
const auto suffixes = mimeType.suffixes();
|
|
for(const auto& suffix: suffixes) {
|
|
nameFilter += "*.";
|
|
nameFilter += suffix;
|
|
nameFilter += ' ';
|
|
}
|
|
nameFilter[nameFilter.length() - 1] = ')';
|
|
}
|
|
nameFilters << nameFilter;
|
|
}
|
|
setNameFilters(nameFilters);
|
|
}
|
|
|
|
void FileDialog::setLabelTextControl(QFileDialog::DialogLabel label, const QString& text) {
|
|
switch(label) {
|
|
case QFileDialog::LookIn:
|
|
ui->lookInLabel->setText(text);
|
|
break;
|
|
case QFileDialog::FileName:
|
|
ui->fileNameLabel->setText(text);
|
|
break;
|
|
case QFileDialog::FileType:
|
|
ui->fileTypeLabel->setText(text);
|
|
break;
|
|
case QFileDialog::Accept:
|
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setText(text);
|
|
break;
|
|
case QFileDialog::Reject:
|
|
ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(text);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FileDialog::setLabelText(QFileDialog::DialogLabel label, const QString& text) {
|
|
setLabelExplicitly(label, text);
|
|
setLabelTextControl(label, text);
|
|
}
|
|
|
|
QString FileDialog::labelText(QFileDialog::DialogLabel label) const {
|
|
QString text;
|
|
switch(label) {
|
|
case QFileDialog::LookIn:
|
|
text = ui->lookInLabel->text();
|
|
break;
|
|
case QFileDialog::FileName:
|
|
text = ui->fileNameLabel->text();
|
|
break;
|
|
case QFileDialog::FileType:
|
|
text = ui->fileTypeLabel->text();
|
|
break;
|
|
case QFileDialog::Accept:
|
|
ui->buttonBox->button(QDialogButtonBox::Ok)->text();
|
|
break;
|
|
case QFileDialog::Reject:
|
|
ui->buttonBox->button(QDialogButtonBox::Cancel)->text();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
void FileDialog::updateSaveButtonText(bool saveOnFolder) {
|
|
if(fileMode_ != QFileDialog::Directory
|
|
&& acceptMode_ == QFileDialog::AcceptSave) {
|
|
// change save button to open button when there is a dir with the save name,
|
|
// otherwise restore it to a save button again
|
|
if(!saveOnFolder) {
|
|
QStringList parsedNames = parseNames();
|
|
if(!parsedNames.isEmpty()) {
|
|
auto info = proxyModel_->fileInfoFromPath(directoryPath_.child(parsedNames.at(0).toLocal8Bit().constData()));
|
|
if(info && info->isDir()) {
|
|
saveOnFolder = true;
|
|
}
|
|
}
|
|
}
|
|
if(saveOnFolder) {
|
|
setLabelTextControl(QFileDialog::Accept, tr("&Open"));
|
|
}
|
|
else {
|
|
// restore save button text appropriately
|
|
if(isLabelExplicitlySet(QFileDialog::Accept)) {
|
|
setLabelTextControl(QFileDialog::Accept, explicitLabels_[QFileDialog::Accept]);
|
|
}
|
|
else {
|
|
setLabelTextControl(QFileDialog::Accept, tr("&Save"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FileDialog::updateAcceptButtonState() {
|
|
bool enable(false);
|
|
if(fileMode_ != QFileDialog::Directory) {
|
|
if(acceptMode_ == QFileDialog::AcceptOpen)
|
|
{
|
|
if(firstSelectedDir()) {
|
|
// enable "open" button if a dir is selected
|
|
enable = true;
|
|
}
|
|
else {
|
|
// enable "open" button when there is a file whose name is listed
|
|
QStringList parsedNames = parseNames();
|
|
for(auto& name: parsedNames) {
|
|
if(proxyModel_->indexFromPath(directoryPath_.child(name.toLocal8Bit().constData())).isValid()) {
|
|
enable = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(acceptMode_ == QFileDialog::AcceptSave) {
|
|
// enable "save" button when there is a name or a dir selection
|
|
if(!ui->fileName->text().isEmpty()) {
|
|
enable = true;
|
|
}
|
|
else if(firstSelectedDir()) {
|
|
enable = true;
|
|
}
|
|
}
|
|
}
|
|
else if(fileMode_ == QFileDialog::Directory
|
|
&& acceptMode_ != QFileDialog::AcceptSave) {
|
|
QStringList parsedNames = parseNames();
|
|
if(parsedNames.isEmpty()) {
|
|
// in the dir mode, the current dir will be opened
|
|
// if no dir is selected and the name list is empty
|
|
enable = true;
|
|
}
|
|
else {
|
|
for(auto& name: parsedNames) {
|
|
auto info = proxyModel_->fileInfoFromPath(directoryPath_.child(name.toLocal8Bit().constData()));
|
|
if(info && info->isDir()) {
|
|
// the name of a dir is listed
|
|
enable = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
enable = true;
|
|
}
|
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable);
|
|
}
|
|
|
|
bool FileDialog::FileDialogFilter::filterAcceptsRow(const ProxyFolderModel* /*model*/, const std::shared_ptr<const FileInfo> &info) const {
|
|
if(dlg_->fileMode_ == QFileDialog::Directory) {
|
|
// we only want to select directories
|
|
if(!info->isDir()) { // not a dir
|
|
// NOTE: here we ignore dlg_->options_& QFileDialog::ShowDirsOnly option.
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
// we want to select files, so all directories can be shown regardless of their names
|
|
if(info->isDir()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool nameMatched = false;
|
|
auto& name = info->displayName();
|
|
for(const auto& pattern: patterns_) {
|
|
if(pattern.exactMatch(name)) {
|
|
nameMatched = true;
|
|
break;
|
|
}
|
|
}
|
|
return nameMatched;
|
|
}
|
|
|
|
void FileDialog::FileDialogFilter::update() {
|
|
// update filename patterns
|
|
patterns_.clear();
|
|
QString nameFilter = dlg_->currentNameFilter_;
|
|
// if the filter contains (...), only get the part between the parenthesis.
|
|
auto left = nameFilter.indexOf('(');
|
|
if(left != -1) {
|
|
++left;
|
|
auto right = nameFilter.indexOf(')', left);
|
|
if(right == -1) {
|
|
right = nameFilter.length();
|
|
}
|
|
nameFilter = nameFilter.mid(left, right - left);
|
|
}
|
|
// parse the "*.ext1 *.ext2 *.ext3 ..." list into QRegExp objects
|
|
auto globs = nameFilter.simplified().split(' ');
|
|
for(const auto& glob: globs) {
|
|
patterns_.emplace_back(QRegExp(glob, Qt::CaseInsensitive, QRegExp::Wildcard));
|
|
}
|
|
}
|
|
|
|
} // namespace Fm
|