Skip to content

Commit 6563ab7

Browse files
committed
fix(NativeFileDialog): Bind file dialog to Window
Signed-off-by: paulober <[email protected]>
1 parent 653b83e commit 6563ab7

File tree

8 files changed

+107
-23
lines changed

8 files changed

+107
-23
lines changed

src/imagewriter.cpp

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "iconimageprovider.h"
2323
#include "nativefiledialog.h"
2424
#include <QQmlApplicationEngine>
25+
#include <QQuickWindow>
2526
#endif
2627
#include <archive.h>
2728
#include <archive_entry.h>
@@ -109,7 +110,9 @@ ImageWriter::ImageWriter(QObject *parent)
109110
_translations(),
110111
_trans(nullptr),
111112
_refreshIntervalOverrideMinutes(-1),
112-
_refreshJitterOverrideMinutes(-1)
113+
_refreshJitterOverrideMinutes(-1),
114+
_piConnectToken(),
115+
_mainWindow(nullptr)
113116
{
114117
// Initialize CacheManager
115118
_cacheManager = new CacheManager(this);
@@ -294,6 +297,23 @@ ImageWriter::ImageWriter(QObject *parent)
294297
}
295298
}
296299

300+
void ImageWriter::setMainWindow(QObject *window)
301+
{
302+
#ifndef CLI_ONLY_BUILD
303+
// Convert QObject to QWindow
304+
_mainWindow = qobject_cast<QWindow*>(window);
305+
if (!_mainWindow) {
306+
// If it's not a QWindow directly, try to get the window from a QQuickWindow
307+
QQuickWindow *quickWindow = qobject_cast<QQuickWindow*>(window);
308+
if (quickWindow) {
309+
_mainWindow = quickWindow;
310+
}
311+
}
312+
#else
313+
Q_UNUSED(window);
314+
#endif
315+
}
316+
297317
QString ImageWriter::getNativeOpenFileName(const QString &title,
298318
const QString &initialDir,
299319
const QString &filter)
@@ -302,7 +322,7 @@ QString ImageWriter::getNativeOpenFileName(const QString &title,
302322
if (!NativeFileDialog::areNativeDialogsAvailable()) {
303323
return QString();
304324
}
305-
return NativeFileDialog::getOpenFileName(title, initialDir, filter);
325+
return NativeFileDialog::getOpenFileName(title, initialDir, filter, _mainWindow);
306326
#else
307327
Q_UNUSED(title);
308328
Q_UNUSED(initialDir);
@@ -1392,10 +1412,11 @@ void ImageWriter::openFileDialog(const QString &title, const QString &filter)
13921412
if (path.isEmpty() || !fi.exists() || !fi.isReadable() )
13931413
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
13941414

1395-
// Use native file dialog - QtWidgets fallback removed
1415+
// Use native file dialog with modal behavior to main window
13961416
QString filename = NativeFileDialog::getOpenFileName(tr("Select image"),
13971417
path,
1398-
filter);
1418+
filter,
1419+
_mainWindow);
13991420

14001421
// Process the selected file if one was chosen
14011422
if (!filename.isEmpty())

src/imagewriter.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <QVariant>
2020
#ifndef CLI_ONLY_BUILD
2121
#include <QQmlEngine>
22+
#include <QWindow>
2223
#endif
2324
#include <QNetworkReply>
2425
#include "config.h"
@@ -197,6 +198,9 @@ class ImageWriter : public QObject
197198
const QString &initialDir = QString(),
198199
const QString &filter = QString());
199200

201+
/* Set the main window for modal file dialogs */
202+
Q_INVOKABLE void setMainWindow(QObject *window);
203+
200204
/* Read text file contents */
201205
Q_INVOKABLE QString readFileContents(const QString &filePath);
202206

@@ -368,6 +372,9 @@ protected slots:
368372
int _refreshJitterOverrideMinutes;
369373
// Session-only storage for Raspberry Pi Connect token
370374
QString _piConnectToken;
375+
#ifndef CLI_ONLY_BUILD
376+
QWindow *_mainWindow;
377+
#endif
371378

372379
void _parseCompressedFile();
373380
void _parseXZFile();

src/linux/nativefiledialog_linux.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public slots:
6060

6161
QString NativeFileDialog::getFileNameNative(const QString &title,
6262
const QString &initialDir, const QString &filter,
63-
bool saveDialog)
63+
bool saveDialog, void *parentWindow)
6464
{
6565

6666
QDBusConnection bus = QDBusConnection::sessionBus();
@@ -79,6 +79,18 @@ QString NativeFileDialog::getFileNameNative(const QString &title,
7979
return QString(); // QML callsites will handle fallback
8080
}
8181

82+
// Prepare parent window identifier for modal behavior
83+
QString parentWindowId = "";
84+
if (parentWindow) {
85+
QWindow *window = static_cast<QWindow*>(parentWindow);
86+
// Format: "x11:<xid>" for X11 windows
87+
// For Wayland it would be "wayland:<handle>" but that's more complex
88+
WId winId = window->winId();
89+
if (winId != 0) {
90+
parentWindowId = QString("x11:%1").arg(winId, 0, 16);
91+
}
92+
}
93+
8294
// Prepare arguments for the portal call
8395
QVariantMap options;
8496
options["modal"] = true;
@@ -128,10 +140,9 @@ QString NativeFileDialog::getFileNameNative(const QString &title,
128140
options["handle_token"] = token;
129141

130142
QString method = saveDialog ? "SaveFile" : "OpenFile";
131-
QString parentWindow = ""; // Could be set to X11 window ID for proper modal behavior
132143

133-
// Make the async call
134-
QDBusReply<QDBusObjectPath> reply = interface.call(method, parentWindow, title, options);
144+
// Make the async call with parent window identifier for modal behavior
145+
QDBusReply<QDBusObjectPath> reply = interface.call(method, parentWindowId, title, options);
135146

136147
if (!reply.isValid()) {
137148
qDebug() << "NativeFileDialog: Portal call failed:" << reply.error().message();

src/mac/nativefiledialog_macos.mm

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <QTimer>
1010
#include <QEventLoop>
1111
#include <QCoreApplication>
12+
#include <QWindow>
1213
#include <Cocoa/Cocoa.h>
1314

1415
namespace {
@@ -52,7 +53,7 @@ QString convertQtFilterToMacOS(const QString &qtFilter)
5253

5354
QString NativeFileDialog::getFileNameNative(const QString &title,
5455
const QString &initialDir, const QString &filter,
55-
bool saveDialog)
56+
bool saveDialog, void *parentWindow)
5657
{
5758
// Defer dialog presentation to avoid interfering with Qt QML object destruction
5859
QString result;
@@ -61,8 +62,18 @@ QString convertQtFilterToMacOS(const QString &qtFilter)
6162
// Process events once to allow QML cleanup, then show dialog with minimal delay
6263
QCoreApplication::processEvents();
6364

65+
// Get the native NSWindow if parentWindow is provided
66+
NSWindow *nsParentWindow = nil;
67+
if (parentWindow) {
68+
QWindow *window = static_cast<QWindow*>(parentWindow);
69+
NSView *view = reinterpret_cast<NSView*>(window->winId());
70+
if (view) {
71+
nsParentWindow = [view window];
72+
}
73+
}
74+
6475
// Use a very short timer to minimize delay while still avoiding Qt conflicts
65-
QTimer::singleShot(1, [&]() {
76+
QTimer::singleShot(1, [&, nsParentWindow]() {
6677
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
6778

6879
NSString *nsTitle = title.isEmpty() ? nil : title.toNSString();
@@ -97,7 +108,15 @@ QString convertQtFilterToMacOS(const QString &qtFilter)
97108
}
98109
}
99110

100-
NSInteger buttonPressed = [panel runModal];
111+
NSInteger buttonPressed;
112+
if (nsParentWindow) {
113+
// Run as sheet modal to parent window
114+
buttonPressed = [panel runModalForWindow:nsParentWindow];
115+
} else {
116+
// Run as application modal dialog
117+
buttonPressed = [panel runModal];
118+
}
119+
101120
if (buttonPressed == NSModalResponseOK) {
102121
NSURL *url = [panel URL];
103122
modalResult = [url path];
@@ -124,7 +143,15 @@ QString convertQtFilterToMacOS(const QString &qtFilter)
124143
}
125144
}
126145

127-
NSInteger buttonPressed = [panel runModal];
146+
NSInteger buttonPressed;
147+
if (nsParentWindow) {
148+
// Run as sheet modal to parent window
149+
buttonPressed = [panel runModalForWindow:nsParentWindow];
150+
} else {
151+
// Run as application modal dialog
152+
buttonPressed = [panel runModal];
153+
}
154+
128155
if (buttonPressed == NSModalResponseOK) {
129156
NSArray *urls = [panel URLs];
130157
if ([urls count] > 0) {

src/main.qml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ ApplicationWindow {
3232

3333
title: qsTr("Raspberry Pi Imager v%1").arg(imageWriter.constantVersion())
3434

35+
Component.onCompleted: {
36+
// Set the main window for modal file dialogs
37+
imageWriter.setMainWindow(window)
38+
}
39+
3540
onClosing: function (close) {
3641
if (wizardContainer.isWriting && !forceQuit) {
3742
close.accepted = false;

src/nativefiledialog.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,29 @@
1515
bool NativeFileDialog::s_forceQmlDialogs = false;
1616

1717
QString NativeFileDialog::getOpenFileName(const QString &title,
18-
const QString &initialDir, const QString &filter)
18+
const QString &initialDir, const QString &filter,
19+
void *parentWindow)
1920
{
2021
// Check if we should use native dialogs
2122
if (!areNativeDialogsAvailable()) {
2223
// No C++ fallback; QML callsites handle fallback
2324
return QString();
2425
}
2526

26-
return getFileNameNative(title, initialDir, filter, false);
27+
return getFileNameNative(title, initialDir, filter, false, parentWindow);
2728
}
2829

2930
QString NativeFileDialog::getSaveFileName(const QString &title,
30-
const QString &initialDir, const QString &filter)
31+
const QString &initialDir, const QString &filter,
32+
void *parentWindow)
3133
{
3234
// Check if we should use native dialogs
3335
if (!areNativeDialogsAvailable()) {
3436
// No C++ fallback; QML callsites handle fallback
3537
return QString();
3638
}
3739

38-
return getFileNameNative(title, initialDir, filter, true);
40+
return getFileNameNative(title, initialDir, filter, true, parentWindow);
3941
}
4042

4143
bool NativeFileDialog::areNativeDialogsAvailable()

src/nativefiledialog.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,26 @@ class NativeFileDialog
2424
* @param title Dialog title
2525
* @param initialDir Initial directory to show
2626
* @param filter File type filters (Qt format: "Images (*.png *.jpg);;All files (*)")
27+
* @param parentWindow Parent window for modal behavior (optional)
2728
* @return Selected file path, or empty string if cancelled
2829
*/
2930
static QString getOpenFileName(const QString &title = QString(),
3031
const QString &initialDir = QString(),
31-
const QString &filter = QString());
32+
const QString &filter = QString(),
33+
void *parentWindow = nullptr);
3234

3335
/**
3436
* @brief Shows a native save file dialog
3537
* @param title Dialog title
3638
* @param initialDir Initial directory to show
3739
* @param filter File type filters (Qt format: "Images (*.png *.jpg);;All files (*)")
40+
* @param parentWindow Parent window for modal behavior (optional)
3841
* @return Selected file path, or empty string if cancelled
3942
*/
4043
static QString getSaveFileName(const QString &title = QString(),
4144
const QString &initialDir = QString(),
42-
const QString &filter = QString());
45+
const QString &filter = QString(),
46+
void *parentWindow = nullptr);
4347

4448
/**
4549
* @brief Check if native dialogs are available
@@ -57,7 +61,7 @@ class NativeFileDialog
5761
// Platform-specific implementations (implemented in platform-specific files)
5862
static QString getFileNameNative(const QString &title,
5963
const QString &initialDir, const QString &filter,
60-
bool saveDialog);
64+
bool saveDialog, void *parentWindow);
6165
static bool areNativeDialogsAvailablePlatform();
6266

6367
// Flag to force QML dialogs

src/windows/nativefiledialog_windows.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <QStandardPaths>
88
#include <QDir>
99
#include <QDebug>
10+
#include <QWindow>
1011
#include <windows.h>
1112
#include <commdlg.h>
1213

@@ -40,9 +41,8 @@ QString convertQtFilterToWindows(const QString &qtFilter)
4041

4142
QString NativeFileDialog::getFileNameNative(const QString &title,
4243
const QString &initialDir, const QString &filter,
43-
bool saveDialog)
44-
{ // Windows dialogs will be modal to the application
45-
44+
bool saveDialog, void *parentWindow)
45+
{
4646
OPENFILENAME ofn;
4747
ZeroMemory(&ofn, sizeof(ofn));
4848

@@ -63,9 +63,16 @@ QString NativeFileDialog::getFileNameNative(const QString &title,
6363
}
6464
std::wstring initialDirStr = QDir::toNativeSeparators(dir).toStdWString();
6565

66+
// Get native window handle for modal behavior
67+
HWND hwndOwner = nullptr;
68+
if (parentWindow) {
69+
QWindow *window = static_cast<QWindow*>(parentWindow);
70+
hwndOwner = reinterpret_cast<HWND>(window->winId());
71+
}
72+
6673
// Setup OPENFILENAME structure
6774
ofn.lStructSize = sizeof(ofn);
68-
ofn.hwndOwner = nullptr; // Could get actual window handle if needed
75+
ofn.hwndOwner = hwndOwner; // Set parent window for modal behavior
6976
ofn.lpstrFile = szFile;
7077
ofn.nMaxFile = sizeof(szFile) / sizeof(wchar_t);
7178
ofn.lpstrFilter = filterStr.empty() ? nullptr : filterStr.c_str();

0 commit comments

Comments
 (0)