Skip to content

Commit d1c9576

Browse files
authored
Use native file dialogs on macOS (#4402)
* WIP: Add native file dialog for macOS * WIP: Add file type filters * WIP: Add file format picker * WIP: Change allowed file types * Tune style * Update comments * Add better layout * Fix Linux build * Drop obsolete dependencies * Remember the last dir * Fix Visual Studio project * Fix Windows build
1 parent 8122a65 commit d1c9576

File tree

10 files changed

+319
-63
lines changed

10 files changed

+319
-63
lines changed

requirements/macos.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ cpr
44
fmt
55
gdcm
66
glfw
7-
gtk+3
87
hidapi
98
jpeg-turbo
109
jsoncpp
1110
libharu
1211
libpng
13-
libsigc++
1412
libtiff
1513
libzip
1614
llvm@14

scripts/auto_update_vcxproj.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"config.h",
2020
"config_cmake.h",
2121
# macOS-specific files
22+
"mrfiledialogcocoa.h",
2223
"mrtouchpadcocoahandler.h",
2324
}
2425

scripts/check_vcxproj.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"config.h",
1313
"config_cmake.h",
1414
# macOS-specific files
15+
"mrfiledialogcocoa.h",
1516
"mrtouchpadcocoahandler.h",
1617
}
1718

source/MRViewer/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ IF(NOT MESHLIB_BUILD_VOXELS)
1111
set(MRVIEWER_NO_VOXELS ON)
1212
ENDIF()
1313

14-
IF(WIN32 OR MR_EMSCRIPTEN)
14+
IF(WIN32 OR APPLE OR MR_EMSCRIPTEN)
1515
set(MRVIEWER_NO_GTK ON)
1616
ENDIF()
1717

source/MRViewer/MRFileDialog.cpp

Lines changed: 27 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#include "MRFileDialog.h"
2+
#include "MRFileDialogInternal.h"
3+
24
#include "MRViewerFwd.h"
35
#include "MRColorTheme.h"
46
#include "MRCommandLoop.h"
@@ -15,7 +17,9 @@
1517
#include <clocale>
1618

1719
#ifndef _WIN32
18-
#ifndef MRVIEWER_NO_GTK
20+
#if defined( __APPLE__ )
21+
#include "MRFileDialogCocoa.h"
22+
#elif !defined( MRVIEWER_NO_GTK )
1923
#include <gtk/gtk.h>
2024
#endif
2125
#else
@@ -93,15 +97,8 @@ EMSCRIPTEN_KEEPALIVE void emsFreeFSCallback()
9397
namespace
9498
{
9599

96-
struct FileDialogParameters : MR::FileParameters
97-
{
98-
bool folderDialog{false}; // open dialog only
99-
bool multiselect{true}; // open dialog only
100-
bool saveDialog{false}; // true for save dialog, false for open
101-
};
102-
103100
#if defined( _WIN32 )
104-
std::vector<std::filesystem::path> windowsDialog( const FileDialogParameters& params = {} )
101+
std::vector<std::filesystem::path> windowsDialog( const MR::detail::FileDialogParameters& params = {} )
105102
{
106103
std::vector<std::filesystem::path> res;
107104
//<SnippetRefCounts>
@@ -236,25 +233,7 @@ std::vector<std::filesystem::path> windowsDialog( const FileDialogParameters& pa
236233
}
237234
#else
238235
#ifndef MRVIEWER_NO_GTK
239-
const std::string cLastUsedDirKey = "lastUsedDir";
240-
241-
std::string getCurrentFolder( const FileDialogParameters& params )
242-
{
243-
if ( !params.baseFolder.empty() )
244-
return MR::utf8string( params.baseFolder );
245-
246-
auto& cfg = MR::Config::instance();
247-
if ( cfg.hasJsonValue( cLastUsedDirKey ) )
248-
{
249-
auto lastUsedDir = cfg.getJsonValue( cLastUsedDirKey );
250-
if ( lastUsedDir.isString() )
251-
return lastUsedDir.asString();
252-
}
253-
254-
return MR::utf8string( MR::GetHomeDirectory() );
255-
}
256-
257-
std::tuple<GtkFileChooserAction, std::string> gtkDialogParameters( const FileDialogParameters& params )
236+
std::tuple<GtkFileChooserAction, std::string> gtkDialogParameters( const MR::detail::FileDialogParameters& params )
258237
{
259238
if ( params.folderDialog )
260239
return { GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, params.multiselect ? "Open Folders" : "Open Folder" };
@@ -264,7 +243,7 @@ std::tuple<GtkFileChooserAction, std::string> gtkDialogParameters( const FileDia
264243
return { GTK_FILE_CHOOSER_ACTION_OPEN, params.multiselect ? "Open Files" : "Open File" };
265244
}
266245

267-
std::vector<std::filesystem::path> gtkDialog( const FileDialogParameters& params = {} )
246+
std::vector<std::filesystem::path> gtkDialog( const MR::detail::FileDialogParameters& params = {} )
268247
{
269248
// Gtk has a nasty habit of overriding the locale to "".s
270249
std::optional<std::string> localeStr;
@@ -286,20 +265,13 @@ std::vector<std::filesystem::path> gtkDialog( const FileDialogParameters& params
286265
};
287266

288267
auto [action, title] = gtkDialogParameters( params );
289-
#if defined( __APPLE__ )
290-
auto* dialog = gtk_file_chooser_native_new( title.c_str(), NULL, action, params.saveDialog ? "_Save" : "_Open", "_Cancel" );
291-
MR_FINALLY {
292-
gtk_native_dialog_destroy( GTK_NATIVE_DIALOG( dialog ) );
293-
};
294-
#else
295268
auto* dialog = gtk_file_chooser_dialog_new( title.c_str(), NULL, action, NULL, NULL );
296269
MR_FINALLY {
297270
gtk_widget_destroy( dialog );
298271
};
299272

300273
gtk_dialog_add_button( GTK_DIALOG( dialog ), params.saveDialog ? "_Save" : "_Open", GTK_RESPONSE_ACCEPT );
301274
gtk_dialog_add_button( GTK_DIALOG( dialog ), "_Cancel", GTK_RESPONSE_CANCEL );
302-
#endif
303275

304276
auto* chooser = GTK_FILE_CHOOSER( dialog );
305277

@@ -315,11 +287,9 @@ std::vector<std::filesystem::path> gtkDialog( const FileDialogParameters& params
315287
{
316288
auto nextSeparatorPos = filter.extensions.find( ";", separatorPos );
317289
auto ext = filter.extensions.substr( separatorPos, nextSeparatorPos - separatorPos );
318-
#if defined( __APPLE__ )
319-
if ( ext == "*.*" )
320-
ext = "*";
321-
#endif
290+
322291
gtk_file_filter_add_pattern( fileFilter, ext.c_str() );
292+
323293
if ( nextSeparatorPos == std::string::npos )
324294
break;
325295
separatorPos = nextSeparatorPos + 1;
@@ -328,7 +298,7 @@ std::vector<std::filesystem::path> gtkDialog( const FileDialogParameters& params
328298
gtk_file_chooser_add_filter( chooser, fileFilter ); // the chooser takes ownership of the filter
329299
}
330300

331-
const auto currentFolder = getCurrentFolder( params );
301+
const auto currentFolder = MR::detail::getCurrentFolder( params.baseFolder );
332302
gtk_file_chooser_set_current_folder( chooser, currentFolder.c_str() );
333303

334304
if ( !params.fileName.empty() )
@@ -367,28 +337,15 @@ std::vector<std::filesystem::path> gtkDialog( const FileDialogParameters& params
367337
results.emplace_back( std::move( filepath ) );
368338
}
369339

370-
auto& cfg = MR::Config::instance();
371-
cfg.setJsonValue( cLastUsedDirKey, gtk_file_chooser_get_current_folder( chooser ) );
340+
MR::detail::setCurrentFolder( gtk_file_chooser_get_current_folder( chooser ) );
372341
}
373342
else if ( responseId != GTK_RESPONSE_CANCEL )
374343
{
375344
spdlog::warn( "GTK dialog failed" );
376345
}
377-
#if defined( __APPLE__ )
378-
// on macOS the main window remains unfocused after the file dialog is closed
379-
MR::CommandLoop::appendCommand( []
380-
{
381-
glfwFocusWindow( MR::Viewer::instance()->window );
382-
} );
383-
#endif
384346
};
385-
#if defined( __APPLE__ )
386-
onResponse( gtk_native_dialog_run( GTK_NATIVE_DIALOG( dialog ) ) );
387-
gtk_native_dialog_hide( GTK_NATIVE_DIALOG( dialog ) );
388-
#else // __APPLE__
389347
onResponse( gtk_dialog_run( GTK_DIALOG( dialog ) ) );
390348
gtk_widget_hide( dialog );
391-
#endif // __APPLE__
392349

393350
return results;
394351
}
@@ -422,7 +379,7 @@ namespace MR
422379

423380
std::filesystem::path openFileDialog( const FileParameters& params )
424381
{
425-
FileDialogParameters parameters{ params };
382+
detail::FileDialogParameters parameters{ params };
426383
parameters.folderDialog = false;
427384
parameters.multiselect = false;
428385
parameters.saveDialog = false;
@@ -432,6 +389,8 @@ std::filesystem::path openFileDialog( const FileParameters& params )
432389
std::vector<std::filesystem::path> results;
433390
#if defined( _WIN32 )
434391
results = windowsDialog( parameters );
392+
#elif defined( __APPLE__ )
393+
results = detail::runCocoaFileDialog( parameters );
435394
#elif !defined( MRVIEWER_NO_GTK )
436395
results = gtkDialog( parameters );
437396
#endif
@@ -469,7 +428,7 @@ void openFileDialogAsync( std::function<void( const std::filesystem::path& )> ca
469428

470429
std::vector<std::filesystem::path> openFilesDialog( const FileParameters& params )
471430
{
472-
FileDialogParameters parameters{ params };
431+
detail::FileDialogParameters parameters{ params };
473432
parameters.folderDialog = false;
474433
parameters.multiselect = true;
475434
parameters.saveDialog = false;
@@ -479,6 +438,8 @@ std::vector<std::filesystem::path> openFilesDialog( const FileParameters& params
479438
std::vector<std::filesystem::path> results;
480439
#if defined( _WIN32 )
481440
results = windowsDialog( parameters );
441+
#elif defined( __APPLE__ )
442+
results = detail::runCocoaFileDialog( parameters );
482443
#elif !defined( MRVIEWER_NO_GTK )
483444
results = gtkDialog( parameters );
484445
#endif
@@ -512,7 +473,7 @@ std::filesystem::path openFolderDialog( std::filesystem::path baseFolder )
512473
// Windows dialog does not support forward slashes between folders
513474
baseFolder.make_preferred();
514475

515-
FileDialogParameters parameters;
476+
detail::FileDialogParameters parameters;
516477
parameters.baseFolder = baseFolder;
517478
parameters.folderDialog = true;
518479
parameters.multiselect = false;
@@ -521,6 +482,8 @@ std::filesystem::path openFolderDialog( std::filesystem::path baseFolder )
521482
std::vector<std::filesystem::path> results;
522483
#if defined( _WIN32 )
523484
results = windowsDialog( parameters );
485+
#elif defined( __APPLE__ )
486+
results = detail::runCocoaFileDialog( parameters );
524487
#elif !defined( MRVIEWER_NO_GTK )
525488
results = gtkDialog( parameters );
526489
#endif
@@ -561,7 +524,7 @@ std::vector<std::filesystem::path> openFoldersDialog( std::filesystem::path base
561524
// Windows dialog does not support forward slashes between folders
562525
baseFolder.make_preferred();
563526

564-
FileDialogParameters parameters;
527+
detail::FileDialogParameters parameters;
565528
parameters.baseFolder = baseFolder;
566529
parameters.folderDialog = true;
567530
parameters.multiselect = true;
@@ -570,6 +533,8 @@ std::vector<std::filesystem::path> openFoldersDialog( std::filesystem::path base
570533
std::vector<std::filesystem::path> results;
571534
#if defined( _WIN32 )
572535
results = windowsDialog( parameters );
536+
#elif defined( __APPLE__ )
537+
results = detail::runCocoaFileDialog( parameters );
573538
#elif !defined( MRVIEWER_NO_GTK )
574539
results = gtkDialog( parameters );
575540
#endif
@@ -580,7 +545,7 @@ std::vector<std::filesystem::path> openFoldersDialog( std::filesystem::path base
580545

581546
std::filesystem::path saveFileDialog( const FileParameters& params /*= {} */ )
582547
{
583-
FileDialogParameters parameters{ params };
548+
detail::FileDialogParameters parameters{ params };
584549
parameters.folderDialog = false;
585550
parameters.multiselect = false;
586551
parameters.saveDialog = true;
@@ -590,6 +555,8 @@ std::filesystem::path saveFileDialog( const FileParameters& params /*= {} */ )
590555
std::vector<std::filesystem::path> results;
591556
#if defined( _WIN32 )
592557
results = windowsDialog( parameters );
558+
#elif defined( __APPLE__ )
559+
results = detail::runCocoaFileDialog( parameters );
593560
#elif !defined( MRVIEWER_NO_GTK )
594561
results = gtkDialog( parameters );
595562
#endif
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
#include "MRFileDialogInternal.h"
4+
5+
namespace MR::detail
6+
{
7+
8+
/// Open Cocoa file dialog
9+
MRVIEWER_API std::vector<std::filesystem::path> runCocoaFileDialog( const FileDialogParameters& params );
10+
11+
} // namespace MR::detail

0 commit comments

Comments
 (0)