fortrangoingonforty/sniffly / 21a5c7b

Browse files

use native macOS NSOpenPanel for file picker to fix focus loss and lag

Co-Authored-By: mfwolffe <wolffemf@dukes.jmu.edu>
Authored by espadonne
SHA
21a5c7be8a7bdbe41a2f79aa6ae363fb8391901f
Parents
bd09f9e
Tree
6f07dff

3 changed files

StatusFile+-
M meson.build 3 2
A src/core/macos_file_picker.m 39 0
M src/gui/gtk_app.f90 40 26
meson.buildmodified
@@ -1,7 +1,7 @@
1
 # Sniffly - Pure Fortran GUI Disk Analyzer
1
 # Sniffly - Pure Fortran GUI Disk Analyzer
2
 # Build configuration using Meson
2
 # Build configuration using Meson
3
 
3
 
4
-project('sniffly', ['fortran', 'c'],
4
+project('sniffly', ['fortran', 'c', 'objc'],
5
   version: '0.2.4',  # Also maintained in VERSION file
5
   version: '0.2.4',  # Also maintained in VERSION file
6
   license: 'MIT',
6
   license: 'MIT',
7
   default_options: [
7
   default_options: [
@@ -71,6 +71,7 @@ version_f90 = configure_file(
71
 # C helper sources (for file system operations)
71
 # C helper sources (for file system operations)
72
 c_sources = [
72
 c_sources = [
73
   'src/core/dir_helpers.c',
73
   'src/core/dir_helpers.c',
74
+  'src/core/macos_file_picker.m',
74
 ]
75
 ]
75
 
76
 
76
 # Fortran source files
77
 # Fortran source files
@@ -146,7 +147,7 @@ endif
146
 # -headerpad_max_install_names reserves space for dylibbundler to rewrite library paths
147
 # -headerpad_max_install_names reserves space for dylibbundler to rewrite library paths
147
 link_args = []
148
 link_args = []
148
 if host_machine.system() == 'darwin'
149
 if host_machine.system() == 'darwin'
149
-  link_args += ['-Wl,-headerpad_max_install_names']
150
+  link_args += ['-Wl,-headerpad_max_install_names', '-framework', 'Cocoa']
150
 endif
151
 endif
151
 
152
 
152
 # Build executable
153
 # Build executable
src/core/macos_file_picker.madded
@@ -0,0 +1,39 @@
1
+// macOS Native File Picker using NSOpenPanel
2
+// Provides a proper native file picker that maintains window focus
3
+#import <Cocoa/Cocoa.h>
4
+#include <string.h>
5
+
6
+// Show native macOS folder picker and return selected path
7
+// Returns 0 on success, -1 on cancel/error
8
+// NOTE: Must be called from main thread (GTK callbacks already run on main thread)
9
+int macos_show_folder_picker(char* output_path, size_t max_len) {
10
+    int result = -1;
11
+
12
+    @autoreleasepool {
13
+        NSOpenPanel* panel = [NSOpenPanel openPanel];
14
+
15
+        // Configure for folder selection
16
+        [panel setCanChooseFiles:NO];
17
+        [panel setCanChooseDirectories:YES];
18
+        [panel setAllowsMultipleSelection:NO];
19
+        [panel setMessage:@"Select directory to scan"];
20
+        [panel setPrompt:@"Select"];
21
+
22
+        // Run modal - this properly maintains focus
23
+        NSModalResponse response = [panel runModal];
24
+
25
+        if (response == NSModalResponseOK) {
26
+            NSURL* url = [[panel URLs] firstObject];
27
+            if (url) {
28
+                const char* path = [[url path] UTF8String];
29
+                if (path && strlen(path) < max_len) {
30
+                    strncpy(output_path, path, max_len - 1);
31
+                    output_path[max_len - 1] = '\0';
32
+                    result = 0;
33
+                }
34
+            }
35
+        }
36
+    }
37
+
38
+    return result;
39
+}
src/gui/gtk_app.f90modified
@@ -95,6 +95,16 @@ module gtk_app
95
   character(len=512), save :: pending_synthetic_child_name = ""
95
   character(len=512), save :: pending_synthetic_child_name = ""
96
   integer, save :: synthetic_nav_attempts = 0  ! Count attempts to avoid infinite loops
96
   integer, save :: synthetic_nav_attempts = 0  ! Count attempts to avoid infinite loops
97
 
97
 
98
+  ! C interface for macOS native file picker
99
+  interface
100
+    function macos_show_folder_picker(output_path, max_len) bind(c, name='macos_show_folder_picker')
101
+      import :: c_char, c_int
102
+      character(kind=c_char), dimension(*) :: output_path
103
+      integer(c_int), value :: max_len
104
+      integer(c_int) :: macos_show_folder_picker
105
+    end function macos_show_folder_picker
106
+  end interface
107
+
98
 contains
108
 contains
99
 
109
 
100
   ! Run the Sniffly GTK application
110
   ! Run the Sniffly GTK application
@@ -540,13 +550,14 @@ contains
540
   end subroutine load_custom_css
550
   end subroutine load_custom_css
541
 
551
 
542
   ! Callback when Open Directory button is clicked
552
   ! Callback when Open Directory button is clicked
543
-  ! NOTE: Uses system command for file picking until GTK4 file dialog bindings are available
553
+  ! Uses native macOS NSOpenPanel for fast, focus-preserving file picker
544
   subroutine on_open_dir_clicked(button, user_data) bind(c)
554
   subroutine on_open_dir_clicked(button, user_data) bind(c)
545
     type(c_ptr), value :: button, user_data
555
     type(c_ptr), value :: button, user_data
546
     type(tab_state), pointer :: tab
556
     type(tab_state), pointer :: tab
557
+    character(kind=c_char, len=1024) :: c_path
547
     character(len=1024) :: selected_path
558
     character(len=1024) :: selected_path
548
-    integer :: status
559
+    integer(c_int) :: status
549
-    integer(c_int) :: idle_id
560
+    integer :: i, path_len
550
 
561
 
551
     print *, "Open Directory button clicked!"
562
     print *, "Open Directory button clicked!"
552
 
563
 
@@ -557,34 +568,37 @@ contains
557
       return
568
       return
558
     end if
569
     end if
559
 
570
 
560
-    ! Call helper to show native file picker
571
+    ! Call native macOS file picker (modal, maintains focus)
561
-    call show_native_directory_picker(selected_path, status)
572
+    status = macos_show_folder_picker(c_path, int(len(c_path), c_int))
562
 
573
 
563
-    if (status == 0 .and. len_trim(selected_path) > 0) then
574
+    if (status == 0) then
564
-      ! Restore window focus after dialog using idle callback
575
+      ! Convert C string to Fortran string
565
-      ! (deferred to let macOS finish cleaning up osascript dialog)
576
+      path_len = 0
566
-      if (c_associated(main_window_ptr)) then
577
+      do i = 1, len(c_path)
567
-        idle_id = g_idle_add(c_funloc(restore_window_focus), c_null_ptr)
578
+        if (c_path(i:i) == c_null_char) exit
568
-      end if
579
+        path_len = i
569
-      print *, "Selected directory: ", trim(selected_path)
580
+      end do
570
 
581
 
571
-      ! Update tab scan path (but don't scan yet)
582
+      if (path_len > 0) then
572
-      ! Remove trailing slash if present (C code doesn't like it)
583
+        selected_path = c_path(1:path_len)
573
-      if (len_trim(selected_path) > 1 .and. selected_path(len_trim(selected_path):len_trim(selected_path)) == '/') then
584
+        print *, "Selected directory: ", trim(selected_path)
574
-        tab%scan_path = trim(selected_path(1:len_trim(selected_path)-1))
575
-        print *, "DEBUG: Removed trailing slash from path"
576
-      else
577
-        tab%scan_path = trim(selected_path)
578
-      end if
579
-      print *, "DEBUG: Set tab scan_path to: '", trim(tab%scan_path), "'"
580
-      call set_scan_path(trim(tab%scan_path))
581
 
585
 
582
-      ! Update path display entry
586
+        ! Update tab scan path (but don't scan yet)
583
-      call update_path_entry(trim(tab%scan_path))
587
+        ! Remove trailing slash if present (C code doesn't like it)
588
+        if (len_trim(selected_path) > 1 .and. &
589
+            selected_path(len_trim(selected_path):len_trim(selected_path)) == '/') then
590
+          tab%scan_path = trim(selected_path(1:len_trim(selected_path)-1))
591
+        else
592
+          tab%scan_path = trim(selected_path)
593
+        end if
584
 
594
 
585
-      print *, "Path updated. Click Scan button to scan: ", trim(tab%scan_path)
595
+        call set_scan_path(trim(tab%scan_path))
596
+        call update_path_entry(trim(tab%scan_path))
597
+
598
+        print *, "Path updated. Click Scan button to scan: ", trim(tab%scan_path)
599
+      end if
586
     else
600
     else
587
-      print *, "Directory selection cancelled or failed"
601
+      print *, "Directory selection cancelled"
588
     end if
602
     end if
589
   end subroutine on_open_dir_clicked
603
   end subroutine on_open_dir_clicked
590
 
604