5
5
Authors: Bernard Teo, Michael Labbe
6
6
7
7
Note: We do not check for malloc failure on Linux - Linux overcommits memory!
8
+ Note: The GTK4 implementation does not distinguish between local and network files,
9
+ so it is possible for the file picker to return a network URI instead of a local filename.
8
10
*/
9
11
10
12
#include < assert.h>
11
13
#include < gtk/gtk.h>
12
14
#if defined(GDK_WINDOWING_X11)
15
+ #if GTK_MAJOR_VERSION == 3
13
16
#include < gdk/gdkx.h>
17
+ #elif GTK_MAJOR_VERSION == 4
18
+ #include < gdk/x11/gdkx.h>
19
+ #endif
14
20
#endif
15
21
#include < stddef.h>
16
22
#include < stdio.h>
19
25
20
26
#include " nfd.h"
21
27
28
+ #define UNSUPPORTED_ERROR () static_assert(false , " Unsupported GTK version, this is an NFD bug." )
29
+
22
30
namespace {
23
31
24
32
template <typename T>
@@ -37,6 +45,14 @@ struct FreeCheck_Guard {
37
45
}
38
46
};
39
47
48
+ template <typename T>
49
+ struct GUnref_Guard {
50
+ T* data;
51
+ GUnref_Guard (T* object) noexcept : data(object) {}
52
+ ~GUnref_Guard () { g_object_unref (data); }
53
+ T* get () const noexcept { return data; }
54
+ };
55
+
40
56
/* current error */
41
57
const char * g_errorstr = nullptr ;
42
58
@@ -278,33 +294,107 @@ Pair_GtkFileFilter_FileExtension* AddFiltersToDialogWithMap(GtkFileChooser* choo
278
294
return map;
279
295
}
280
296
281
- void SetDefaultPath (GtkFileChooser* chooser, const char * defaultPath) {
282
- if (!defaultPath || !*defaultPath) return ;
297
+ /*
298
+ Note: GTK+ manual recommends not specifically setting the default path.
299
+ We do it anyway in order to be consistent across platforms.
283
300
284
- /* GTK+ manual recommends not specifically setting the default path.
285
- We do it anyway in order to be consistent across platforms.
301
+ If consistency with the native OS is preferred,
302
+ then this function should be made a no-op.
303
+ */
304
+ #if GTK_MAJOR_VERSION == 3
305
+ nfdresult_t SetDefaultPath (GtkFileChooser* chooser, const char * defaultPath) {
306
+ if (!defaultPath || !*defaultPath) return NFD_OKAY;
286
307
287
- If consistency with the native OS is preferred, this is the line
288
- to comment out. -ml */
289
308
gtk_file_chooser_set_current_folder (chooser, defaultPath);
309
+ return NFD_OKAY;
310
+ }
311
+ #elif GTK_MAJOR_VERSION == 4
312
+ nfdresult_t SetDefaultPath (GtkFileChooser* chooser, const char * defaultPath) {
313
+ if (!defaultPath || !*defaultPath) return NFD_OKAY;
314
+
315
+ GUnref_Guard<GFile> file (g_file_new_for_path (defaultPath));
316
+
317
+ if (!gtk_file_chooser_set_current_folder (chooser, file.get (), NULL )) {
318
+ NFDi_SetError (" Failed to set default path." );
319
+ return NFD_ERROR;
320
+ }
321
+ return NFD_OKAY;
290
322
}
323
+ #endif
291
324
292
325
void SetDefaultName (GtkFileChooser* chooser, const char * defaultName) {
293
326
if (!defaultName || !*defaultName) return ;
294
327
295
328
gtk_file_chooser_set_current_name (chooser, defaultName);
296
329
}
297
330
331
+ #if GTK_MAJOR_VERSION == 3
332
+ nfdresult_t GetSingleFileNameForOpen (GtkWidget* widget, char ** outPath) {
333
+ char * tmp_outPath = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
334
+ if (tmp_outPath) {
335
+ *outPath = tmp_outPath;
336
+ return NFD_OKAY;
337
+ }
338
+ return NFD_ERROR;
339
+ }
340
+ nfdresult_t GetSingleFileNameForSave (GtkWidget* widget, char ** outPath) {
341
+ return GetSingleFilenameForOpen (widget, outPath);
342
+ }
343
+ #elif GTK_MAJOR_VERSION == 4
344
+ nfdresult_t GetSingleFileNameForOpen (GtkWidget* widget, char ** outPath) {
345
+ GUnref_Guard<GFile> file (gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget)));
346
+ char * tmp_outPath = g_file_get_path (file.get ());
347
+ if (tmp_outPath) {
348
+ *outPath = tmp_outPath;
349
+ return NFD_OKAY;
350
+ }
351
+ // it's not a local file... we should copy it
352
+ GFileIOStream* localFileIOStream;
353
+ GFile* localFile = g_file_new_tmp (NULL , &localFileIOStream, NULL );
354
+ if (!localFile) return NFD_ERROR;
355
+ GUnref_Guard<GFile> localFileGuard (localFile);
356
+ GUnref_Guard<GFileIOStream> localFileIOStreamGuard (localFileIOStream);
357
+ g_io_stream_close (G_IO_STREAM (localFileIOStream), NULL , NULL );
358
+ if (!g_file_copy (file.get (), localFile, G_FILE_COPY_OVERWRITE, NULL , NULL , NULL , NULL ))
359
+ return NFD_ERROR;
360
+ *outPath = g_file_get_path (localFile);
361
+ return NFD_OKAY;
362
+ }
363
+ nfdresult_t GetSingleFileNameForSave (GtkWidget* widget, char ** outPath) {
364
+ GUnref_Guard<GFile> file (gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget)));
365
+ char * tmp_outPath = g_file_get_path (file.get ());
366
+ if (tmp_outPath) {
367
+ *outPath = tmp_outPath;
368
+ return NFD_OKAY;
369
+ }
370
+ // it's not a local file... we balk and say the user cancelled the dialog
371
+ return NFD_CANCEL;
372
+ }
373
+ #endif
374
+
298
375
void WaitForCleanup () {
376
+ #if GTK_MAJOR_VERSION == 3
299
377
while (gtk_events_pending ()) gtk_main_iteration ();
378
+ #elif GTK_MAJOR_VERSION == 4
379
+ while (g_main_context_iteration (NULL , FALSE ))
380
+ ;
381
+ #else
382
+ UNSUPPORTED_ERROR ();
383
+ #endif
300
384
}
301
385
302
386
struct Widget_Guard {
303
387
GtkWidget* data;
304
388
Widget_Guard (GtkWidget* widget) : data(widget) {}
305
389
~Widget_Guard () {
306
390
WaitForCleanup ();
391
+ #if GTK_MAJOR_VERSION == 3
307
392
gtk_widget_destroy (data);
393
+ #elif GTK_MAJOR_VERSION == 4
394
+ gtk_window_destroy (GTK_WINDOW (data));
395
+ #else
396
+ UNSUPPORTED_ERROR ();
397
+ #endif
308
398
WaitForCleanup ();
309
399
}
310
400
};
@@ -362,12 +452,20 @@ void FileActivatedSignalHandler(GtkButton* saveButton, void* userdata) {
362
452
g_free (currentFileName);
363
453
}
364
454
455
+ #if GTK_MAJOR_VERSION == 4
456
+ void DialogResponseHandler (GtkDialog*, gint resp, gpointer out_resp_gp) {
457
+ gint* out_resp = static_cast <gint*>(out_resp_gp);
458
+ *out_resp = resp;
459
+ }
460
+ #endif
461
+
365
462
// wrapper for gtk_dialog_run() that brings the dialog to the front
366
463
// see issues at:
367
464
// https://github.com/btzy/nativefiledialog-extended/issues/31
368
465
// https://github.com/mlabbe/nativefiledialog/pull/92
369
466
// https://github.com/guillaumechereau/noc/pull/11
370
467
gint RunDialogWithFocus (GtkDialog* dialog) {
468
+ #if GTK_MAJOR_VERSION == 3
371
469
#if defined(GDK_WINDOWING_X11)
372
470
gtk_widget_show_all (GTK_WIDGET (dialog)); // show the dialog so that it gets a display
373
471
if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (dialog)))) {
@@ -379,6 +477,20 @@ gint RunDialogWithFocus(GtkDialog* dialog) {
379
477
}
380
478
#endif
381
479
return gtk_dialog_run (dialog);
480
+ #elif GTK_MAJOR_VERSION == 4
481
+ // TODO: the X11 popup issues
482
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE );
483
+ gtk_widget_show (GTK_WIDGET (dialog));
484
+ gint resp = 0 ;
485
+ g_signal_connect (G_OBJECT (dialog),
486
+ " response" ,
487
+ G_CALLBACK (DialogResponseHandler),
488
+ static_cast <gpointer>(&resp));
489
+ while (resp == 0 ) g_main_context_iteration (NULL , TRUE );
490
+ return resp;
491
+ #else
492
+ UNSUPPORTED_ERROR ();
493
+ #endif
382
494
}
383
495
384
496
} // namespace
@@ -395,7 +507,14 @@ void NFD_ClearError(void) {
395
507
396
508
nfdresult_t NFD_Init (void ) {
397
509
// Init GTK
398
- if (!gtk_init_check (NULL , NULL )) {
510
+ #if GTK_MAJOR_VERSION == 3
511
+ if (!gtk_init_check (NULL , NULL ))
512
+ #elif GTK_MAJOR_VERSION == 4
513
+ if (!gtk_init_check ())
514
+ #else
515
+ UNSUPPORTED_ERROR ();
516
+ #endif
517
+ {
399
518
NFDi_SetError (" Failed to initialize GTK+ with gtk_init_check." );
400
519
return NFD_ERROR;
401
520
}
@@ -430,13 +549,12 @@ nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
430
549
AddFiltersToDialog (GTK_FILE_CHOOSER (widget), filterList, filterCount);
431
550
432
551
/* Set the default path */
433
- SetDefaultPath (GTK_FILE_CHOOSER (widget), defaultPath);
552
+ nfdresult_t res = SetDefaultPath (GTK_FILE_CHOOSER (widget), defaultPath);
553
+ if (res != NFD_OKAY) return res;
434
554
435
555
if (RunDialogWithFocus (GTK_DIALOG (widget)) == GTK_RESPONSE_ACCEPT) {
436
556
// write out the file name
437
- *outPath = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
438
-
439
- return NFD_OKAY;
557
+ return GetSingleFileNameForOpen (widget, outPath);
440
558
} else {
441
559
return NFD_CANCEL;
442
560
}
@@ -465,7 +583,8 @@ nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
465
583
AddFiltersToDialog (GTK_FILE_CHOOSER (widget), filterList, filterCount);
466
584
467
585
/* Set the default path */
468
- SetDefaultPath (GTK_FILE_CHOOSER (widget), defaultPath);
586
+ nfdresult_t res = SetDefaultPath (GTK_FILE_CHOOSER (widget), defaultPath);
587
+ if (res != NFD_OKAY) return res;
469
588
470
589
if (RunDialogWithFocus (GTK_DIALOG (widget)) == GTK_RESPONSE_ACCEPT) {
471
590
// write out the file name
@@ -495,8 +614,10 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
495
614
496
615
GtkWidget* saveButton = gtk_dialog_add_button (GTK_DIALOG (widget), " _Save" , GTK_RESPONSE_ACCEPT);
497
616
498
- // Prompt on overwrite
617
+ // Prompt on overwrite (GTK3 only, because GTK4 automatically prompts)
618
+ #if GTK_MAJOR_VERSION == 3
499
619
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (widget), TRUE );
620
+ #endif
500
621
501
622
/* Build the filter list */
502
623
ButtonClickedArgs buttonClickedArgs;
@@ -505,7 +626,8 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
505
626
AddFiltersToDialogWithMap (GTK_FILE_CHOOSER (widget), filterList, filterCount);
506
627
507
628
/* Set the default path */
508
- SetDefaultPath (GTK_FILE_CHOOSER (widget), defaultPath);
629
+ nfdresult_t res = SetDefaultPath (GTK_FILE_CHOOSER (widget), defaultPath);
630
+ if (res != NFD_OKAY) return res;
509
631
510
632
/* Set the default file name */
511
633
SetDefaultName (GTK_FILE_CHOOSER (widget), defaultName);
@@ -526,9 +648,7 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
526
648
527
649
if (result == GTK_RESPONSE_ACCEPT) {
528
650
// write out the file name
529
- *outPath = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
530
-
531
- return NFD_OKAY;
651
+ return GetSingleFileNameForSave (widget, outPath);
532
652
} else {
533
653
return NFD_CANCEL;
534
654
}
@@ -548,13 +668,13 @@ nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath)
548
668
Widget_Guard widgetGuard (widget);
549
669
550
670
/* Set the default path */
551
- SetDefaultPath (GTK_FILE_CHOOSER (widget), defaultPath);
671
+ nfdresult_t res = SetDefaultPath (GTK_FILE_CHOOSER (widget), defaultPath);
672
+ if (res != NFD_OKAY) return res;
552
673
553
674
if (RunDialogWithFocus (GTK_DIALOG (widget)) == GTK_RESPONSE_ACCEPT) {
554
675
// write out the file name
555
- *outPath = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
556
-
557
- return NFD_OKAY;
676
+ // we don't support non-local files, so the behaviour is the same as the save dialog
677
+ return GetSingleFileNameForSave (widget, outPath);
558
678
} else {
559
679
return NFD_CANCEL;
560
680
}
0 commit comments