Files
create/src/Gui/FileDialog.cpp
timpieces a2dabd42e0 MacOS: Disable actions while file dialogs are open (#26169)
- Any action part of the application menu will trigger based on shortcut
  when a native file dialog is open
- Another way to fix this is to switch out the application menu, but
  afaict it requires writing native objective-c code. I think that's too
  much complexity just to get cmd+c/cmd+v in these file dialogs
- For now, just disable the actions so that select-all, rotate-left, etc
  don't trigger when pressed in these dialogs
- I've implemented an RAII wrapper to disable this. It should take
  pointers which should be fine because all of these dialog calls are
  blocking (so in principle nothing can change underneath).

I'm quite sure this won't have any adverse effects on other platforms,
but will need help from other developers to test in case.
2025-12-23 09:18:08 -06:00

1215 lines
38 KiB
C++

/***************************************************************************
* Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 51 Franklin Street, *
* Fifth Floor, Boston, MA 02110-1301, USA *
* *
***************************************************************************/
#include <QApplication>
#include <QButtonGroup>
#include <QCompleter>
#include <QCryptographicHash>
#include <QDialogButtonBox>
#include <QDir>
#include <QGridLayout>
#include <QGroupBox>
#include <QLineEdit>
#include <QMenuBar>
#include <QPushButton>
#include <QRadioButton>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QResizeEvent>
#include <QStandardPaths>
#include <QStyle>
#include <QUrl>
#include <Base/Parameter.h>
#include <App/Application.h>
#include "FileDialog.h"
#include "MainWindow.h"
#include "Tools.h"
using namespace Gui;
// An raii-helper struct to disable actions while dialogs are open
// At least on macos, shortcuts for enabled actions will still trigger while dialogs are open
struct ActionDisabler
{
ActionDisabler()
{
auto mainWin = Gui::getMainWindow();
if (!mainWin) {
return;
}
QMenuBar* menuBar = mainWin->menuBar();
if (!menuBar) {
return;
}
auto actions = menuBar->actions();
actionsToReenable.reserve(actions.size());
for (auto action : actions) {
if (action->isEnabled()) {
action->setEnabled(false);
actionsToReenable.push_back(action);
}
}
}
~ActionDisabler()
{
for (auto action : actionsToReenable) {
if (action) {
action->setEnabled(true);
}
}
}
FC_DISABLE_COPY(ActionDisabler)
std::vector<QAction*> actionsToReenable {};
};
bool DialogOptions::dontUseNativeFileDialog()
{
#if defined(USE_QT_DIALOGS)
constexpr bool notNativeDialog = true;
#else
constexpr bool notNativeDialog = false;
#endif
ParameterGrp::handle group = App::GetApplication()
.GetUserParameter()
.GetGroup("BaseApp")
->GetGroup("Preferences")
->GetGroup("Dialog");
return group->GetBool("DontUseNativeDialog", notNativeDialog);
}
bool DialogOptions::dontUseNativeColorDialog()
{
#if defined(USE_QT_DIALOGS)
constexpr bool notNativeDialog = true;
#else
constexpr bool notNativeDialog = false;
#endif
ParameterGrp::handle group = App::GetApplication()
.GetUserParameter()
.GetGroup("BaseApp")
->GetGroup("Preferences")
->GetGroup("Dialog");
return group->GetBool("DontUseNativeColorDialog", notNativeDialog);
}
/* TRANSLATOR Gui::FileDialog */
FileDialog::FileDialog(QWidget* parent)
: QFileDialog(parent)
{
connect(this, &QFileDialog::filterSelected, this, &FileDialog::onSelectedFilter);
}
FileDialog::~FileDialog() = default;
void FileDialog::onSelectedFilter(const QString& /*filter*/)
{
QRegularExpression rx(QLatin1String(R"(\(\*.(\w+))"));
QString suf = selectedNameFilter();
auto match = rx.match(suf);
if (match.hasMatch()) {
suf = match.captured(1);
setDefaultSuffix(suf);
}
}
QList<QUrl> FileDialog::fetchSidebarUrls()
{
QStringList list;
list << QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
list << QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
list << QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
list << QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
list << QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
list << getWorkingDirectory();
list << restoreLocation();
list << QDir::currentPath();
QList<QUrl> urls;
for (const auto& it : list) {
if (QFileInfo::exists(it)) {
urls << QUrl::fromLocalFile(it);
}
}
return urls;
}
bool FileDialog::hasSuffix(const QString& ext) const
{
QRegularExpression rx(
QStringLiteral("\\*.(%1)\\W").arg(ext),
QRegularExpression::CaseInsensitiveOption
);
QStringList filters = nameFilters();
for (const auto& str : filters) {
if (rx.match(str).hasMatch()) {
return true;
}
}
return false;
}
void FileDialog::accept()
{
// When saving to a file make sure that the entered filename ends with the selected
// file filter
if (acceptMode() == QFileDialog::AcceptSave) {
QStringList files = selectedFiles();
if (!files.isEmpty()) {
QString ext = this->defaultSuffix();
QString file = files.front();
QString suffix = QFileInfo(file).suffix();
// #0001928: do not add a suffix if a file with suffix is entered
// #0002209: make sure that the entered suffix is part of one of the filters
if (!ext.isEmpty() && (suffix.isEmpty() || !hasSuffix(suffix))) {
file = QStringLiteral("%1.%2").arg(file, ext);
// That's the built-in line edit
auto fileNameEdit = this->findChild<QLineEdit*>(QStringLiteral("fileNameEdit"));
if (fileNameEdit) {
fileNameEdit->setText(file);
}
}
}
}
QFileDialog::accept();
}
void FileDialog::getSuffixesDescription(QStringList& suffixes, const QString* suffixDescriptions)
{
QRegularExpression rx;
// start the raw string with a (
// match a *, a . and at least one word character (a-z, A-Z, 0-9, _) with \*\.\w+
// end the raw string with a )
rx.setPattern(QLatin1String(R"(\*\.\w+)"));
QRegularExpressionMatchIterator i = rx.globalMatch(*suffixDescriptions);
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString suffix = match.captured(0);
suffixes << suffix;
}
}
/**
* This is a convenience static function that will return a file name selected by the user. The file
* does not have to exist.
*/
QString FileDialog::getSaveFileName(
QWidget* parent,
const QString& caption,
const QString& dir,
const QString& filter,
QString* selectedFilter,
Options options
)
{
ActionDisabler actionDisabler {};
QString dirName = dir;
bool hasFilename = false;
if (dirName.isEmpty()) {
dirName = getWorkingDirectory();
}
else {
QFileInfo fi(dir);
if (fi.isRelative()) {
dirName = getWorkingDirectory();
dirName += QLatin1String("/");
dirName += fi.fileName();
}
if (!fi.fileName().isEmpty()) {
hasFilename = true;
}
// get the suffix for the filter: use the selected filter if there is one,
// otherwise find the first valid suffix in the complete list of filters
const QString* filterToSearch;
if (selectedFilter && !selectedFilter->isEmpty()) {
filterToSearch = selectedFilter;
}
else {
filterToSearch = &filter;
}
QStringList filterSuffixes;
getSuffixesDescription(filterSuffixes, filterToSearch);
const QString fiSuffix = fi.suffix();
const QString dotSuffix = QLatin1String("*.") + fiSuffix; // To match with filterSuffixes
if (fiSuffix.isEmpty() || !filterSuffixes.contains(dotSuffix)) {
// there is no suffix or not a suffix that matches the filter, so
// default to the first suffix of the filter
if (!filterSuffixes.isEmpty()) {
dirName += filterSuffixes[0].mid(1);
}
}
}
QString windowTitle = caption;
if (windowTitle.isEmpty()) {
windowTitle = FileDialog::tr("Save As");
}
// NOTE: We must not change the specified file name afterwards as we may return the name of an
// already existing file. Hence we must extract the first matching suffix from the filter list
// and append it before showing the file dialog.
QString file;
if (DialogOptions::dontUseNativeFileDialog()) {
QList<QUrl> urls = fetchSidebarUrls();
options |= QFileDialog::DontUseNativeDialog;
FileDialog dlg(parent);
dlg.setOptions(options);
dlg.setWindowTitle(windowTitle);
dlg.setSidebarUrls(urls);
auto iconprov = std::make_unique<FileIconProvider>();
dlg.setIconProvider(iconprov.get());
dlg.setFileMode(QFileDialog::AnyFile);
dlg.setAcceptMode(QFileDialog::AcceptSave);
dlg.setDirectory(dirName);
if (hasFilename) {
dlg.selectFile(dirName);
}
dlg.setNameFilters(filter.split(QLatin1String(";;")));
if (selectedFilter && !selectedFilter->isEmpty()) {
dlg.selectNameFilter(*selectedFilter);
}
dlg.onSelectedFilter(dlg.selectedNameFilter());
dlg.setOption(QFileDialog::HideNameFilterDetails, false);
dlg.setOption(QFileDialog::DontConfirmOverwrite, false);
if (dlg.exec() == QDialog::Accepted) {
if (selectedFilter) {
*selectedFilter = dlg.selectedNameFilter();
}
file = dlg.selectedFiles().constFirst();
}
}
else {
file = QFileDialog::getSaveFileName(parent, windowTitle, dirName, filter, selectedFilter, options);
file = QDir::fromNativeSeparators(file);
}
if (!file.isEmpty()) {
setWorkingDirectory(file);
return file;
}
else {
return {};
}
}
/**
* This is a convenience static function that will return an existing directory selected by the user.
*/
QString FileDialog::getExistingDirectory(
QWidget* parent,
const QString& caption,
const QString& dir,
Options options
)
{
ActionDisabler actionDisabler {};
QString path = QFileDialog::getExistingDirectory(parent, caption, dir, options);
// valid path was selected
if (!path.isEmpty()) {
QDir d(path);
path = d.path(); // get path in Qt manner
}
return path;
}
/**
* This is a convenience static function that returns an existing file selected by the user.
* If the user pressed Cancel, it returns a null string.
*/
QString FileDialog::getOpenFileName(
QWidget* parent,
const QString& caption,
const QString& dir,
const QString& filter,
QString* selectedFilter,
Options options
)
{
ActionDisabler actionDisabler {};
QString dirName = dir;
if (dirName.isEmpty()) {
dirName = getWorkingDirectory();
}
QString windowTitle = caption;
if (windowTitle.isEmpty()) {
windowTitle = FileDialog::tr("Open");
}
QString file;
if (DialogOptions::dontUseNativeFileDialog()) {
QList<QUrl> urls = fetchSidebarUrls();
options |= QFileDialog::DontUseNativeDialog;
FileDialog dlg(parent);
dlg.setOptions(options);
dlg.setWindowTitle(windowTitle);
dlg.setSidebarUrls(urls);
auto iconprov = std::make_unique<FileIconProvider>();
dlg.setIconProvider(iconprov.get());
dlg.setFileMode(QFileDialog::ExistingFile);
dlg.setAcceptMode(QFileDialog::AcceptOpen);
dlg.setDirectory(dirName);
dlg.setNameFilters(filter.split(QLatin1String(";;")));
dlg.setOption(QFileDialog::HideNameFilterDetails, false);
if (selectedFilter && !selectedFilter->isEmpty()) {
dlg.selectNameFilter(*selectedFilter);
}
if (dlg.exec() == QDialog::Accepted) {
if (selectedFilter) {
*selectedFilter = dlg.selectedNameFilter();
}
file = dlg.selectedFiles().constFirst();
}
}
else {
file = QFileDialog::getOpenFileName(parent, windowTitle, dirName, filter, selectedFilter, options);
file = QDir::fromNativeSeparators(file);
}
if (!file.isEmpty()) {
setWorkingDirectory(file);
return file;
}
else {
return {};
}
}
/**
* This is a convenience static function that will return one or more existing files selected by the
* user.
*/
QStringList FileDialog::getOpenFileNames(
QWidget* parent,
const QString& caption,
const QString& dir,
const QString& filter,
QString* selectedFilter,
Options options
)
{
ActionDisabler actionDisabler {};
QString dirName = dir;
if (dirName.isEmpty()) {
dirName = getWorkingDirectory();
}
QString windowTitle = caption;
if (windowTitle.isEmpty()) {
windowTitle = FileDialog::tr("Open");
}
QStringList files;
if (DialogOptions::dontUseNativeFileDialog()) {
QList<QUrl> urls = fetchSidebarUrls();
options |= QFileDialog::DontUseNativeDialog;
FileDialog dlg(parent);
dlg.setOptions(options);
dlg.setWindowTitle(windowTitle);
dlg.setSidebarUrls(urls);
auto iconprov = std::make_unique<FileIconProvider>();
dlg.setIconProvider(iconprov.get());
dlg.setFileMode(QFileDialog::ExistingFiles);
dlg.setAcceptMode(QFileDialog::AcceptOpen);
dlg.setDirectory(dirName);
dlg.setNameFilters(filter.split(QLatin1String(";;")));
dlg.setOption(QFileDialog::HideNameFilterDetails, false);
if (selectedFilter && !selectedFilter->isEmpty()) {
dlg.selectNameFilter(*selectedFilter);
}
if (dlg.exec() == QDialog::Accepted) {
if (selectedFilter) {
*selectedFilter = dlg.selectedNameFilter();
}
files = dlg.selectedFiles();
}
}
else {
files = QFileDialog::getOpenFileNames(parent, windowTitle, dirName, filter, selectedFilter, options);
for (auto& file : files) {
file = QDir::fromNativeSeparators(file);
}
}
if (!files.isEmpty()) {
setWorkingDirectory(files.front());
}
return files;
}
QString FileDialog::workingDirectory;
/**
* Returns the working directory for the file dialog. This path can be used in
* combination with getSaveFileName(), getOpenFileName(), getOpenFileNames() or
* getExistingDirectory() to open the dialog in this path.
*/
QString FileDialog::getWorkingDirectory()
{
return workingDirectory;
}
/**
* Sets the working directory to \a dir for the file dialog.
* If \a dir is a file then the path only is taken.
* getWorkingDirectory() returns the working directory.
*/
void FileDialog::setWorkingDirectory(const QString& dir)
{
QString dirName = dir;
if (!dir.isEmpty()) {
QFileInfo info(dir);
if (!info.exists() || info.isFile()) {
dirName = info.absolutePath();
}
else {
dirName = info.absoluteFilePath();
}
}
workingDirectory = dirName;
saveLocation(dirName);
}
/*!
* \brief Return the last location where a file save or load dialog was used.
* \return QString
*/
QString FileDialog::restoreLocation()
{
std::string path = App::GetApplication().Config()["UserHomePath"];
Base::Reference<ParameterGrp> hPath = App::GetApplication()
.GetUserParameter()
.GetGroup("BaseApp")
->GetGroup("Preferences")
->GetGroup("General");
std::string dir = hPath->GetASCII("FileOpenSavePath", path.c_str());
QFileInfo fi(QString::fromUtf8(dir.c_str()));
if (!fi.exists()) {
dir = path;
}
return QString::fromUtf8(dir.c_str());
}
/*!
* \brief Save the last location where a file save or load dialog was used.
* \param dirName
*/
void FileDialog::saveLocation(const QString& dirName)
{
Base::Reference<ParameterGrp> hPath = App::GetApplication()
.GetUserParameter()
.GetGroup("BaseApp")
->GetGroup("Preferences")
->GetGroup("General");
hPath->SetASCII("FileOpenSavePath", dirName.toUtf8());
}
// ======================================================================
/* TRANSLATOR Gui::FileOptionsDialog */
FileOptionsDialog::FileOptionsDialog(QWidget* parent, Qt::WindowFlags fl)
: QFileDialog(parent, fl)
, extensionPos(ExtensionRight)
{
extensionButton = new QPushButton(this);
extensionButton->setText(tr("Extended"));
setOption(QFileDialog::DontUseNativeDialog);
// search for the grid layout and add the new button
auto grid = this->findChild<QGridLayout*>();
grid->addWidget(extensionButton, 4, 2, Qt::AlignLeft);
connect(extensionButton, &QPushButton::clicked, this, &FileOptionsDialog::toggleExtension);
}
FileOptionsDialog::~FileOptionsDialog() = default;
void FileOptionsDialog::accept()
{
// Fixes a bug of the default implementation when entering an asterisk
auto filename = this->findChild<QLineEdit*>();
QString fn = filename->text();
if (fn.startsWith(QLatin1String("*"))) {
QFileInfo fi(fn);
QString ext = fi.suffix();
ext.prepend(QLatin1String("*."));
QStringList filters = this->nameFilters();
bool ok = false;
// Compare the given suffix with the suffixes of all filters
QString filter;
for (const auto& it : filters) {
if (it.contains(ext)) {
filter = it;
ok = true;
break;
}
}
// if no appropriate filter was found the add the 'All files' filter
if (!ok) {
filter = tr("All files (*.*)");
filters << filter;
setNameFilters(filters);
}
// empty the line edit
filename->blockSignals(true);
filename->clear();
filename->blockSignals(false);
selectNameFilter(filter);
return;
}
else if (!fn.isEmpty()) {
QFileInfo fi(fn);
QString ext = fi.completeSuffix();
QRegularExpression rx(QLatin1String(R"(\(\*.(\w+))"));
QString suf = selectedNameFilter();
auto match = rx.match(suf);
if (match.hasMatch()) {
suf = match.captured(1);
}
if (ext.isEmpty()) {
setDefaultSuffix(suf);
}
else if (ext.toLower() != suf.toLower()) {
fn = QStringLiteral("%1.%2").arg(fn, suf);
selectFile(fn);
// That's the built-in line edit (fixes Debian bug #811200)
auto fileNameEdit = this->findChild<QLineEdit*>(QStringLiteral("fileNameEdit"));
if (fileNameEdit) {
fileNameEdit->setText(fn);
}
}
}
QFileDialog::accept();
}
void FileOptionsDialog::toggleExtension()
{
if (extensionWidget) {
bool showIt = !extensionWidget->isVisible();
if (showIt) {
oldSize = size();
QSize s(extensionWidget->sizeHint()
.expandedTo(extensionWidget->minimumSize())
.boundedTo(extensionWidget->maximumSize()));
if (extensionPos == ExtensionRight) {
setFixedSize(width() + s.width(), height());
}
else {
setFixedSize(width(), height() + s.height());
}
extensionWidget->show();
}
else {
extensionWidget->hide();
setFixedSize(oldSize);
}
}
}
void FileOptionsDialog::setOptionsWidget(FileOptionsDialog::ExtensionPosition pos, QWidget* w, bool show)
{
extensionPos = pos;
extensionWidget = w;
if (extensionWidget->parentWidget() != this) {
extensionWidget->setParent(this);
}
auto grid = this->findChild<QGridLayout*>();
if (extensionPos == ExtensionRight) {
int cols = grid->columnCount();
grid->addWidget(extensionWidget, 0, cols, -1, -1);
setMinimumHeight(extensionWidget->height());
}
else if (extensionPos == ExtensionBottom) {
int rows = grid->rowCount();
grid->addWidget(extensionWidget, rows, 0, -1, -1);
setMinimumWidth(extensionWidget->width());
}
oldSize = size();
w->hide();
if (show) {
toggleExtension();
}
}
QWidget* FileOptionsDialog::getOptionsWidget() const
{
return extensionWidget;
}
// ======================================================================
/**
* Constructs an empty file icon provider called \a name, with the parent \a parent.
*/
FileIconProvider::FileIconProvider() = default;
FileIconProvider::~FileIconProvider() = default;
QIcon FileIconProvider::icon(IconType type) const
{
return QFileIconProvider::icon(type);
}
QIcon FileIconProvider::icon(const QFileInfo& info) const
{
auto toUrl = [](const QFileInfo& info) {
QFileInfo fi(info);
fi.makeAbsolute();
QString fileName = fi.absoluteFilePath();
if (fi.isSymLink()) {
fileName = fi.symLinkTarget();
}
return QUrl::fromLocalFile(fileName).toString();
};
auto urlToThumbnail = [](const QString& filename) {
QString hash = QString::fromLatin1(
QCryptographicHash::hash(filename.toUtf8(), QCryptographicHash::Md5).toHex()
);
QString cache = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
return QStringLiteral("%1/thumbnails/normal/%2.png").arg(cache, hash);
};
auto iconFromFile = [](const QString& filename) {
if (QFile::exists(filename)) {
QIcon icon(filename);
if (!icon.isNull()) {
return icon;
}
}
return QIcon(QStringLiteral(":/icons/freecad-doc.png"));
};
if (info.suffix().toLower() == QLatin1String("fcstd")) {
// Check if a thumbnail is available
QString fileName = toUrl(info);
QString thumb = urlToThumbnail(fileName);
return iconFromFile(thumb);
}
else if (info.suffix().toLower().startsWith(QLatin1String("fcstd"))) {
QIcon icon(QStringLiteral(":/icons/freecad-doc.png"));
QIcon darkIcon;
int w = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize);
darkIcon.addPixmap(icon.pixmap(w, w, QIcon::Disabled, QIcon::Off), QIcon::Normal, QIcon::Off);
darkIcon.addPixmap(icon.pixmap(w, w, QIcon::Disabled, QIcon::On), QIcon::Normal, QIcon::On);
return darkIcon;
}
return QFileIconProvider::icon(info);
}
QString FileIconProvider::type(const QFileInfo& info) const
{
return QFileIconProvider::type(info);
}
// --------------------------------------------------------------------
/* TRANSLATOR Gui::FileChooser */
/**
* Constructs a file chooser called \a name with the parent \a parent.
*/
FileChooser::FileChooser(QWidget* parent)
: QWidget(parent)
, md(File)
, accMode(AcceptOpen)
, _filter(QString())
{
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(2);
lineEdit = new QLineEdit(this);
completer = new QCompleter(this);
completer->setMaxVisibleItems(12);
fs_model = new QFileSystemModel(completer);
fs_model->setRootPath(QStringLiteral(""));
completer->setModel(fs_model);
lineEdit->setCompleter(completer);
connect(lineEdit, &QLineEdit::textChanged, this, &FileChooser::fileNameChanged);
connect(lineEdit, &QLineEdit::editingFinished, this, &FileChooser::editingFinished);
button = new QPushButton(QStringLiteral(""), this);
#if defined(Q_OS_MACOS)
button->setAttribute(Qt::WA_LayoutUsesWidgetRect); // layout size from QMacStyle was not correct
#endif
layout->addWidget(lineEdit, 1);
layout->addWidget(button, -1);
connect(button, &QPushButton::clicked, this, &FileChooser::chooseFile);
setFocusProxy(lineEdit);
}
FileChooser::~FileChooser() = default;
void FileChooser::resizeEvent(QResizeEvent* e)
{
button->setFixedHeight(e->size().height());
}
/**
* \property FileChooser::fileName
*
* This property holds the file name.
* Set this property's value with setFileName() and get this property's value with fileName().
*
* \sa fileName(), setFileName().
*/
QString FileChooser::fileName() const
{
return lineEdit->text();
}
void FileChooser::editingFinished()
{
QString le_converted = QDir::fromNativeSeparators(lineEdit->text());
lineEdit->setText(le_converted);
FileDialog::setWorkingDirectory(le_converted);
Q_EMIT fileNameSelected(le_converted);
}
/**
* Sets the file name \a fn.
*/
void FileChooser::setFileName(const QString& fn)
{
lineEdit->setText(fn);
}
/**
* Opens a FileDialog to choose either a file or a directory in dependency of the
* value of the Mode property.
*/
void FileChooser::chooseFile()
{
ActionDisabler actionDisabler {};
QString prechosenDirectory = lineEdit->text();
if (prechosenDirectory.isEmpty()) {
prechosenDirectory = FileDialog::getWorkingDirectory();
}
QFileDialog::Options dlgOpt;
if (DialogOptions::dontUseNativeFileDialog()) {
dlgOpt = QFileDialog::DontUseNativeDialog;
}
QString fn;
if (mode() == File) {
if (acceptMode() == AcceptOpen) {
fn = QFileDialog::getOpenFileName(
this,
tr("Select a File"),
prechosenDirectory,
_filter,
nullptr,
dlgOpt
);
}
else {
fn = QFileDialog::getSaveFileName(
this,
tr("Select a File"),
prechosenDirectory,
_filter,
nullptr,
dlgOpt
);
}
}
else {
QFileDialog::Options option = QFileDialog::ShowDirsOnly | dlgOpt;
fn = QFileDialog::getExistingDirectory(this, tr("Select a Directory"), prechosenDirectory, option);
}
if (!fn.isEmpty()) {
fn = QDir::fromNativeSeparators(fn);
lineEdit->setText(fn);
FileDialog::setWorkingDirectory(fn);
Q_EMIT fileNameSelected(fn);
}
}
/**
* Sets the accept mode.
*/
void FileChooser::setAcceptMode(FileChooser::AcceptMode mode)
{
accMode = mode;
Q_EMIT acceptModeChanged(accMode);
}
/**
* \property FileChooser::mode
*
* This property holds whether the widgets selects either a file or a directory.
* The default value of chooseFile is set to File.
*
* \sa chooseFile(), mode(), setMode().
*/
FileChooser::Mode FileChooser::mode() const
{
return md;
}
/**
* If \a m is File the widget is set to choose a file, otherwise it is set to
* choose a directory.
*/
void FileChooser::setMode(FileChooser::Mode m)
{
md = m;
Q_EMIT modeChanged(md);
}
/**
* \property FileChooser::filter
*
* This property holds the set filter to choose a file. This property is used only if
* FileChooser::Mode is set to File.
*
* \sa chooseFile(), filter(), setFilter().
*/
QString FileChooser::filter() const
{
return _filter;
}
/**
* Sets the filter for choosing a file.
*/
void FileChooser::setFilter(const QString& filter)
{
_filter = filter;
Q_EMIT filterChanged(_filter);
}
/**
* Sets the browse button's text to \a txt.
*/
void FileChooser::setButtonText(const QString& txt)
{
button->setText(txt);
int w1 = 2 * QtTools::horizontalAdvance(button->fontMetrics(), txt);
int w2 = 2 * QtTools::horizontalAdvance(button->fontMetrics(), QStringLiteral(""));
button->setMinimumWidth(std::max(w1, w2));
Q_EMIT buttonTextChanged(txt);
}
/**
* Returns the browse button's text.
*/
QString FileChooser::buttonText() const
{
return button->text();
}
// ----------------------------------------------------------------------
/* TRANSLATOR Gui::SelectModule */
SelectModule::SelectModule(const QString& type, const SelectModule::Dict& types, QWidget* parent)
: QDialog(parent, Qt::WindowTitleHint)
{
setWindowTitle(tr("Select Module"));
groupBox = new QGroupBox(this);
groupBox->setTitle(tr("Open %1 as").arg(type));
group = new QButtonGroup(this);
gridLayout = new QGridLayout(this);
gridLayout->setSpacing(6);
gridLayout->setContentsMargins(9, 9, 9, 9);
gridLayout1 = new QGridLayout(groupBox);
gridLayout1->setSpacing(6);
gridLayout1->setContentsMargins(9, 9, 9, 9);
int index = 0;
for (SelectModule::Dict::const_iterator it = types.begin(); it != types.end(); ++it) {
auto button = new QRadioButton(groupBox);
QRegularExpression rx;
QString filter = it.key();
QString module = it.value();
// ignore file types in (...)
rx.setPattern(QLatin1String(R"(\s+\([\w\*\s\.]+\)$)"));
auto match = rx.match(filter);
if (match.hasMatch()) {
filter = filter.left(match.capturedStart());
}
// ignore Gui suffix in module name
rx.setPattern(QLatin1String("Gui$"));
match = rx.match(module);
if (match.hasMatch()) {
module = module.left(match.capturedStart());
}
button->setText(QStringLiteral("%1 (%2)").arg(filter, module));
button->setObjectName(it.value());
gridLayout1->addWidget(button, index, 0, 1, 1);
group->addButton(button, index);
index++;
}
gridLayout->addWidget(groupBox, 0, 0, 1, 1);
spacerItem = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
gridLayout->addItem(spacerItem, 1, 0, 1, 1);
hboxLayout = new QHBoxLayout();
hboxLayout->setSpacing(6);
hboxLayout->setContentsMargins(0, 0, 0, 0);
spacerItem1 = new QSpacerItem(131, 31, QSizePolicy::Expanding, QSizePolicy::Minimum);
hboxLayout->addItem(spacerItem1);
buttonBox = new QDialogButtonBox(this);
buttonBox->setObjectName(QStringLiteral("buttonBox"));
buttonBox->setStandardButtons(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
buttonBox->button(QDialogButtonBox::Open)->setEnabled(false);
hboxLayout->addWidget(buttonBox);
gridLayout->addLayout(hboxLayout, 2, 0, 1, 1);
// connections
connect(buttonBox, &QDialogButtonBox::accepted, this, &SelectModule::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &SelectModule::reject);
connect(group, &QButtonGroup::idClicked, this, &SelectModule::onButtonClicked);
}
SelectModule::~SelectModule() = default;
void SelectModule::accept()
{
if (group->checkedButton()) {
QDialog::accept();
}
}
void SelectModule::reject()
{
QDialog::reject();
}
void SelectModule::onButtonClicked()
{
QWidget* button = buttonBox->button(QDialogButtonBox::Open);
button->setEnabled(group->checkedButton() != nullptr);
}
QString SelectModule::getModule() const
{
QAbstractButton* button = group->checkedButton();
return (button ? button->objectName() : QString());
}
SelectModule::Dict SelectModule::exportHandler(const QString& fileName, const QString& filter)
{
return exportHandler(QStringList() << fileName, filter);
}
SelectModule::Dict SelectModule::exportHandler(const QStringList& fileNames, const QString& filter)
{
// first check if there is a certain filter selected
SelectModule::Dict dict;
if (!filter.isEmpty()) {
// If an export filter is specified search directly for the module
std::map<std::string, std::string> filterList = App::GetApplication().getExportFilters();
std::map<std::string, std::string>::const_iterator it;
it = filterList.find((const char*)filter.toUtf8());
if (it != filterList.end()) {
QString module = QString::fromLatin1(it->second.c_str());
for (const auto& fileName : fileNames) {
dict[fileName] = module;
}
return dict;
}
}
// the global filter (or no filter) was selected. We now try to sort filetypes that are
// handled by more than one module and ask to the user to select one.
QMap<QString, SelectModule::Dict> filetypeHandler;
QMap<QString, QStringList> fileExtension;
for (const auto& fileName : fileNames) {
QFileInfo fi(fileName);
QString ext = fi.completeSuffix().toLower();
std::map<std::string, std::string> filters = App::GetApplication().getExportFilters(
ext.toLatin1()
);
if (filters.empty()) {
ext = fi.suffix().toLower();
filters = App::GetApplication().getExportFilters(ext.toLatin1());
}
fileExtension[ext].push_back(fileName);
for (const auto& filter : filters) {
filetypeHandler[ext][QString::fromUtf8(filter.first.c_str())] = QString::fromLatin1(
filter.second.c_str()
);
}
// set the default module handler
if (!filters.empty()) {
dict[fileName] = QString::fromLatin1(filters.begin()->second.c_str());
}
}
for (QMap<QString, SelectModule::Dict>::const_iterator it = filetypeHandler.cbegin();
it != filetypeHandler.cend();
++it) {
if (it.value().size() > 1) {
SelectModule dlg(it.key(), it.value(), getMainWindow());
QApplication::beep();
if (dlg.exec()) {
QString mod = dlg.getModule();
const QStringList& files = fileExtension[it.key()];
for (const auto& file : files) {
dict[file] = mod;
}
}
}
}
return dict;
}
SelectModule::Dict SelectModule::importHandler(const QString& fileName, const QString& filter)
{
return importHandler(QStringList() << fileName, filter);
}
SelectModule::Dict SelectModule::importHandler(const QStringList& fileNames, const QString& filter)
{
// first check if there is a certain filter selected
SelectModule::Dict dict;
if (!filter.isEmpty()) {
// If an import filter is specified search directly for the module
std::map<std::string, std::string> filterList = App::GetApplication().getImportFilters();
std::map<std::string, std::string>::const_iterator it;
it = filterList.find((const char*)filter.toUtf8());
if (it != filterList.end()) {
QString module = QString::fromLatin1(it->second.c_str());
for (const auto& fileName : fileNames) {
dict[fileName] = module;
}
return dict;
}
}
// the global filter (or no filter) was selected. We now try to sort filetypes that are
// handled by more than one module and ask to the user to select one.
QMap<QString, SelectModule::Dict> filetypeHandler;
QMap<QString, QStringList> fileExtension;
for (const auto& fileName : fileNames) {
QFileInfo fi(fileName);
QString ext = fi.completeSuffix().toLower();
std::map<std::string, std::string> filters = App::GetApplication().getImportFilters(
ext.toLatin1()
);
if (filters.empty()) {
ext = fi.suffix().toLower();
filters = App::GetApplication().getImportFilters(ext.toLatin1());
}
fileExtension[ext].push_back(fileName);
for (const auto& filter : filters) {
filetypeHandler[ext][QString::fromUtf8(filter.first.c_str())] = QString::fromLatin1(
filter.second.c_str()
);
}
// set the default module handler
if (!filters.empty()) {
dict[fileName] = QString::fromLatin1(filters.begin()->second.c_str());
}
}
for (QMap<QString, SelectModule::Dict>::const_iterator it = filetypeHandler.cbegin();
it != filetypeHandler.cend();
++it) {
if (it.value().size() > 1) {
SelectModule dlg(it.key(), it.value(), getMainWindow());
QApplication::beep();
if (dlg.exec()) {
QString mod = dlg.getModule();
const QStringList& files = fileExtension[it.key()];
for (const auto& file : files) {
dict[file] = mod;
}
}
else {
// Cancelled
return {};
}
}
}
return dict;
}
#include "moc_FileDialog.cpp"