Universal File Dialog, a first step

Alec Jacobson

August 31, 2010

weblog/

I'm switching from Qt to straight Glut with AntTweakBar for my next project. I'm happy about getting rid of Qt. For my small prototyping projects it was too heavy (hard to get a target working on computers that don't have Qt installed) and too much work to add simple ui for tweaking parameters and flipping flags. AntTweakBar doesn't yet solve the deployable problem (I'm trying to figure that out too) but is miles better for prototyping. The only thing I miss from Qt is the native File Save/File Open Dialogs. Since Glut is fairly universal I have taken a stab at writing a small header that could be the start of a universal file dialog "library". I'd only want this library to depend on standard includes and it only needs to implement get_file_save_path and get_file_open_path. Here's what I put in FileDialog.h:
#include <stdio.h>
#define FILE_DIALOG_MAX_BUFFER 1024

// Sets buffer to a path to an existing file 
// buffer[0]=0 on cancel
//
// Usage:
//   char buffer[FILE_DIALOG_MAX_BUFFER];
//   get_open_file_path(buffer);
void get_open_file_path(char buffer[]){
#ifdef __APPLE__
  // For apple use applescript hack
  FILE * output = popen(
    "osascript -e \""
    "   tell application \\\"System Events\\\"\n"
    "           activate\n"
    "           set existing_file to choose file\n"
    "   end tell\n"
    "   set existing_file_path to (POSIX path of (existing_file))\n"
    "\" 2>/dev/null | tr -d '\n' ","r");
  while ( fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL ){
  }
#else
  // For every other machine type 
  printf("Please enter a file path: ");
  gets(buffer);
#endif
}

// Sets buffer to a path to a new/existing file 
// buffer[0]=0 on cancel
//
// Usage:
//   char buffer[FILE_DIALOG_MAX_BUFFER];
//   get_save_file_path(buffer);
void get_save_file_path(char buffer[]){
#ifdef __APPLE__
  // For apple use applescript hack
  // There is currently a bug in Applescript that strips extensions off
  // of chosen existing files in the "choose file name" dialog
  // I'm assuming that will be fixed soon :-)
  FILE * output = popen(
    "osascript -e \""
    "   tell application \\\"System Events\\\"\n"
    "           activate\n"
    "           set existing_file to choose file name\n"
    "   end tell\n"
    "   set existing_file_path to (POSIX path of (existing_file))\n"
    "\" 2>/dev/null | tr -d '\n' ","r");
  while ( fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL ){
  }
#else
  // For every other machine type 
  printf("Please enter a file path: ");
  gets(buffer);
#endif
}
And here's a sample program that calls both functions. Save it in test.c:
#include "FileDialog.h"
#include <stdio.h>

int main(void){
  char buffer[FILE_DIALOG_MAX_BUFFER];

  get_open_file_path(buffer);
  if(buffer[0] == 0)
    printf("Cancelled\n");
  else
    printf("Open file path: %s\n",buffer);

  get_save_file_path(buffer);
  if(buffer[0] == 0)
    printf("Cancelled\n");
  else
    printf("Save file path: %s\n",buffer);

  return 0;
};
Compile and run on a Mac with:
gcc -o test test.c;
./test
Update: I've been looking into a way to do the above for Mac using Carbon or Cocoa but it seems impossible to do correctly without starting up an entire new app... Update: I've noticed many annoying quirks about the above applescript. There's no way to specify only showing certain file extensions (just UTI types but that's not always what you want). It's quite slow to pop up. But most annoying was that focus was not returned to my GLUT app. Here's the hack I place in my app to use applescript to return the focus to my app. Seems to work great:
// you call to FileDialog.h
get_open_file_path(...)
#ifdef __APPLE__
  // Hack to put focus back on app
  FILE * output = popen(                            
    "osascript -e \""
    "   tell application \\\"NameOfYourApp\\\"\n"           
    "           activate\n"
    "   end tell\"\n"                               
    ,"r");                                          
#endif
Of course, replace NameOfYourApp with the name of your app. If you're unsure what this name is, try using the name that appears above your app's icon on the dock when you hover over it.