Using to non-native filedialog on MacOS for DirectoryPathWidget (#4720)

Moved the filebrowsing from the widget to the delegate (MacOS only)
Fixes #4719
This commit is contained in:
Joachim Kohlhammer
2025-10-30 20:19:42 +01:00
committed by GitHub
parent 0ce00abd5f
commit c8da333ab0
2 changed files with 143 additions and 38 deletions

View File

@@ -339,16 +339,16 @@ DirectoryPathWidget::DirectoryPathWidget
{
lineEdit = new QLineEdit();
openButton = new QPushButton(tr("Browse"));
browseButton = new QPushButton(tr("Browse"));
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(lineEdit, 1);
layout->addWidget(openButton, 0);
layout->addWidget(browseButton, 0);
connect(openButton, SIGNAL(clicked()), this, SLOT(openDialog()));
connect(lineEdit, SIGNAL(editingFinished()), this, SLOT(lineEditFinished()));
connect(browseButton, &QPushButton::clicked, this, &DirectoryPathWidget::handleBrowseClicked);
connect(lineEdit, &QLineEdit::editingFinished, this, &DirectoryPathWidget::lineEditFinished);
}
@@ -377,28 +377,63 @@ DirectoryPathWidget::setPlaceholderText
void
DirectoryPathWidget::openDialog
DirectoryPathWidget::setDelegateMode
(bool delegateMode)
{
this->delegateMode = delegateMode;
}
void
DirectoryPathWidget::handleBrowseClicked
()
{
QFileDialog fileDialog(this);
QStringList selectedDirs;
fileDialog.setFileMode(QFileDialog::Directory);
fileDialog.setOptions(QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (delegateMode) {
QTimer::singleShot(0, this, [=]() {
openFileDialog();
});
} else {
openFileDialog();
}
}
void
DirectoryPathWidget::openFileDialog
()
{
#ifdef Q_OS_MACOS
// On macOS in delegate mode, emit a signal and let the delegate handle the dialog.
// This avoids widget lifecycle issues where the editor is destroyed during
// QFileDialog::exec().
if (delegateMode) {
emit browseRequested();
return; // The delegate must take over.
}
#endif
// Standard modal dialog approach for non-delegate mode or non-macOS
QFileDialog dialog(window());
dialog.setFileMode(QFileDialog::Directory);
dialog.setOptions( QFileDialog::ShowDirsOnly
| QFileDialog::DontResolveSymlinks);
QString path = lineEdit->text();
if (path.isEmpty()) {
path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
}
fileDialog.setDirectory(path);
if (fileDialog.exec()) {
selectedDirs = fileDialog.selectedFiles();
}
if (selectedDirs.count() > 0) {
QString dir = selectedDirs.at(0);
if (dir != "") {
lineEdit->setText(dir);
dialog.setDirectory(path);
bool accepted = false;
if (dialog.exec() == QDialog::Accepted) {
const QStringList selectedDirs = dialog.selectedFiles();
if (! selectedDirs.isEmpty()) {
QString selectedDir = selectedDirs.first();
if (! selectedDir.isEmpty()) {
setPath(selectedDir);
accepted = true;
}
}
}
emit editingFinished();
emit editingFinished(accepted);
}
@@ -412,8 +447,8 @@ DirectoryPathWidget::lineEditFinished
}
lineEditAlreadyFinished = true;
if (! openButton->hasFocus()) {
emit editingFinished();
if (! browseButton->hasFocus()) {
emit editingFinished(true);
}
}
@@ -435,8 +470,17 @@ DirectoryPathDelegate::createEditor
Q_UNUSED(index)
DirectoryPathWidget *editor = new DirectoryPathWidget(parent);
editor->setDelegateMode(true);
editor->setPlaceholderText(placeholderText);
connect(editor, SIGNAL(editingFinished()), this, SLOT(commitAndCloseEditor()));
DirectoryPathDelegate *delegate = const_cast<DirectoryPathDelegate*>(this);
#ifdef Q_OS_MACOS
// On macOS, the delegate handles the dialog to prevent lifecycle crashes.
connect(editor, &DirectoryPathWidget::browseRequested, delegate, [delegate, editor, index]() {
delegate->openFileDialogForEditor(editor, index);
});
#endif
connect(editor, &DirectoryPathWidget::editingFinished, delegate, &DirectoryPathDelegate::onEditingFinished);
return editor;
}
@@ -446,8 +490,9 @@ void
DirectoryPathDelegate::setEditorData
(QWidget *editor, const QModelIndex &index) const
{
DirectoryPathWidget *filepath = static_cast<DirectoryPathWidget*>(editor);
filepath->setPath(index.data(Qt::DisplayRole).toString());
if (DirectoryPathWidget *pathEditor = qobject_cast<DirectoryPathWidget*>(editor)) {
pathEditor->setPath(index.data(Qt::EditRole).toString());
}
}
@@ -455,8 +500,9 @@ void
DirectoryPathDelegate::setModelData
(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
DirectoryPathWidget *filepath = static_cast<DirectoryPathWidget*>(editor);
model->setData(index, filepath->getPath(), Qt::DisplayRole);
if (DirectoryPathWidget *pathEditor = qobject_cast<DirectoryPathWidget*>(editor)) {
model->setData(index, pathEditor->getPath(), Qt::EditRole);
}
}
@@ -518,15 +564,62 @@ DirectoryPathDelegate::setPlaceholderText
void
DirectoryPathDelegate::commitAndCloseEditor
()
DirectoryPathDelegate::onEditingFinished
(bool accepted)
{
QWidget *editor = qobject_cast<QWidget*>(sender());
emit commitData(editor);
emit closeEditor(editor);
DirectoryPathWidget *editor = qobject_cast<DirectoryPathWidget*>(sender());
if (editor) {
if (accepted) {
emit commitData(editor);
}
emit closeEditor(editor);
}
}
#ifdef Q_OS_MACOS
void
DirectoryPathDelegate::closeEditorForWidget
(QWidget *editor)
{
if (editor) {
emit closeEditor(editor, QAbstractItemDelegate::NoHint);
}
}
void
DirectoryPathDelegate::openFileDialogForEditor
(QWidget *editor, const QModelIndex &index) const
{
QPointer<QWidget> pathEditor(editor);
QFileDialog dialog(pathEditor.data());
dialog.setFileMode(QFileDialog::Directory);
dialog.setOption(QFileDialog::ShowDirsOnly, true);
dialog.setOption(QFileDialog::DontUseNativeDialog, true);
if (dialog.exec() == QDialog::Accepted) {
// Check if the editor widget still exists before using it
if (!pathEditor.isNull()) {
QString selectedDir = dialog.selectedFiles().first();
// Write directly to the model - this persists the data
QAbstractItemModel *model = const_cast<QAbstractItemModel*>(index.model());
model->setData(index, selectedDir, Qt::EditRole);
}
}
// The editor might have been destroyed during dialog.exec().
// Schedule the closeEditor call to happen safely in the event loop.
if (!pathEditor.isNull()) {
QTimer::singleShot(0, const_cast<DirectoryPathDelegate*>(this), [this, pathEditor]() {
if (!pathEditor.isNull()) {
const_cast<DirectoryPathDelegate*>(this)->closeEditorForWidget(pathEditor.data());
}
});
}
}
#endif
// ListEditWidget //////////////////////////////////////////////////////////////////////
@@ -534,7 +627,7 @@ ListEditWidget::ListEditWidget
(QWidget *parent)
: QWidget(parent)
{
dialog = new QDialog(this);
dialog = new QDialog(parent);
dialog->setModal(true);
title = new QLabel();

View File

@@ -145,20 +145,28 @@ class DirectoryPathWidget: public QWidget
public:
DirectoryPathWidget(QWidget *parent = nullptr);
void setPath(const QString &path);
QString getPath() const;
void setPlaceholderText(const QString &placeholder);
void setDelegateMode(bool delegateMode);
public slots:
void setPath(const QString &path);
signals:
void editingFinished();
void editingFinished(bool accepted);
#ifdef Q_OS_MACOS
void browseRequested();
#endif
private:
QPushButton *openButton;
bool delegateMode = false;
QPushButton *browseButton;
QLineEdit *lineEdit;
bool lineEditAlreadyFinished = false;
private slots:
void openDialog();
void handleBrowseClicked();
void openFileDialog();
void lineEditFinished();
};
@@ -179,12 +187,16 @@ public:
void setMaxWidth(int maxWidth);
void setPlaceholderText(const QString &placeholderText);
private slots:
void commitAndCloseEditor();
private:
int maxWidth = -1;
QString placeholderText;
#ifdef Q_OS_MACOS
void closeEditorForWidget(QWidget *editor);
void openFileDialogForEditor(QWidget *editor, const QModelIndex &index) const;
#endif
private slots:
void onEditingFinished(bool accepted);
};