[AD] native filechooser addon

[ Thread Index | Date Index | More lists.liballeg.org/allegro-developers Archives ]


For things like a level editor, it can be really useful to display the
system's standard file-open dialog to select files - even if the rest of
the program uses just Allegro 5. The attached addon adds two functions
to allow it.

For now, only a GTK filechooser is implemented. But if someone wants to
add Windows/OSX/Qt versions, I think this will be quite nice.

-- 
Elias Pschernig <elias@xxxxxxxxxx>
Index: addons/native_dialog/gtk.c
===================================================================
--- addons/native_dialog/gtk.c	(revision 0)
+++ addons/native_dialog/gtk.c	(revision 0)
@@ -0,0 +1,207 @@
+/* Each of these files implements the same, for different GUI toolkits:
+ * 
+ * gtk.c - GTK file open dialog
+ * osx.c - OSX file open dialog
+ * qt.c  - Qt file open dialog
+ * win.c - Windows file open dialog
+ * 
+ */
+#include <gtk/gtk.h>
+
+#include "allegro5/allegro5.h"
+#include "allegro5/a5_native_dialog.h"
+#include "allegro5/internal/aintern_native_dialog.h"
+#include "allegro5/internal/aintern_memory.h"
+
+typedef struct
+{
+   ALLEGRO_EVENT_SOURCE *event_source;
+   ALLEGRO_EVENT_QUEUE *queue;
+   ALLEGRO_THREAD *thread;
+   ALLEGRO_PATH *initial_path;
+   ALLEGRO_PATH **pathes;
+   int n;
+   char *patterns;
+   int mode;
+} ALLEGRO_NATIVE_FILE_CHOOSER;
+
+/* Only one native file chooser can be open at a time the way this
+ * is written.
+ */
+static ALLEGRO_NATIVE_FILE_CHOOSER *global_file_chooser;
+
+static void destroy(GtkWidget *w, gpointer data)
+{
+    gtk_main_quit();
+}
+
+static void ok(GtkWidget *w, GtkFileSelection *fs)
+{
+    ALLEGRO_NATIVE_FILE_CHOOSER *fc = global_file_chooser;
+    gchar **pathes = gtk_file_selection_get_selections(fs);
+    int n = 0, i;
+    while (pathes[n]) {
+        n++;
+    }
+    fc->n = n;
+    fc->pathes = _AL_MALLOC(n * sizeof(void *));
+    for (i = 0; i < n; i++)
+        fc->pathes[i] = al_path_create(pathes[i]);
+}
+
+/* Because of the way Allegro5's event handling works, we cannot hook
+ * our own event handler in there. Therefore we spawn an extra thread
+ * and simply use the standard GTK main loop.
+ * 
+ * If A5 would allow hooking extra system event handlers, this could
+ * be done without an extra thread - we simply would add a callback
+ * which would be called by A5 whenever the user waits for new events,
+ * and do GTK event handling in there.
+ * 
+ * Running GTK from an extra thread works just fine though.
+ */
+static void *gtk_thread(ALLEGRO_THREAD *thread, void *arg)
+{
+    GtkWidget *window;
+    int argc = 0;
+    char **argv = NULL;
+    gtk_init_check(&argc, &argv);
+
+    /* Create a new file selection widget */
+    window = gtk_file_selection_new("File selection");
+    
+    /* Connect the destroy signal */
+    g_signal_connect(G_OBJECT(window), "destroy",
+        G_CALLBACK(destroy), NULL);
+
+    /* Connect the ok_button */
+    g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(window)->ok_button),
+        "clicked", G_CALLBACK(ok), (gpointer)window);
+
+    /* Connect both buttons to gtk_widget_destroy */
+     g_signal_connect_swapped(
+        GTK_FILE_SELECTION(window)->ok_button,
+        "clicked", G_CALLBACK(gtk_widget_destroy), window);
+    g_signal_connect_swapped(
+        GTK_FILE_SELECTION(window)->cancel_button,
+        "clicked", G_CALLBACK(gtk_widget_destroy), window);
+    
+    if (global_file_chooser->initial_path) {
+        char name[PATH_MAX];
+        al_path_to_string(global_file_chooser->initial_path, name,
+            sizeof name, '/');
+        gtk_file_selection_set_filename(GTK_FILE_SELECTION(window), name);
+    }
+    
+    if (global_file_chooser->mode & ALLEGRO_FILECHOOSER_MULTIPLE)
+        gtk_file_selection_set_select_multiple(
+            GTK_FILE_SELECTION(window), true);
+
+    gtk_widget_show(window);
+    gtk_main();
+
+    ALLEGRO_EVENT event;
+    event.user.type = ALLEGRO_EVENT_NATIVE_FILE_CHOOSER;
+    al_emit_user_event(global_file_chooser->event_source, &event, NULL);
+
+    return NULL;
+}
+
+/* Function: al_spawn_native_file_dialog
+ * 
+ * Parameters:
+ * - queue: Event queue which gets the cancel/ok event.
+ * - initial_path: The initial search path and filename. Can be NULL.
+ * - patterns: A list of semi-colon separated patterns to match. You
+ *   should always include the pattern "*.*" as usually the MIME type
+ *   and not the file pattern is relevant. If no file patterns are
+ *   supported by the native dialog, this parameter is ignored.
+ * - mode: 0, or a combination of the flags below.
+ * 
+ * Possible flags for the 'mode' parameter are:
+ * 
+ * - ALLEGRO_FILECHOOSER_MUST_EXIST: If supported by the native dialog,
+ *   it will not allow entering new names, but just allow existing files
+ *   to be selected. Else it is ignored.
+ * - ALLEGRO_FILECHOOSER_SAVE: If the native dialog system has a
+ *   different dialog for saving (for example one which allows creating
+ *   new directories), it is used. Else ignored.
+ * - ALLEGRO_FILECHOOSER_FOLDER: If there is support for a separate
+ *   dialog to select a folder instead of a file, it will be used.
+ * - ALLEGRO_FILECHOOSER_PICTURES: If a different dialog is available
+ *   for selecting pictures, it is used. Else ignored.
+ * - ALLEGRO_FILECHOOSER_SHOW_HIDDEN: If the platform supports it, also
+ *   hidden files will be shown.
+ * - ALLEGRO_FILECHOOSER_MULTIPLE: If supported, allow selecting
+ *   multiple files.
+ *
+ * Returns:
+ * False if no dialog could be shown.
+ */
+bool al_spawn_native_file_dialog(ALLEGRO_EVENT_QUEUE *queue,
+    ALLEGRO_PATH const *initial_path, char const *patterns, int mode)
+{
+    if (global_file_chooser) return false;
+    
+    ALLEGRO_NATIVE_FILE_CHOOSER *fc;
+    fc = global_file_chooser = _AL_MALLOC(sizeof *fc);
+    memset(fc, 0, sizeof *fc);
+    fc->event_source = al_create_user_event_source();
+    fc->mode = mode;
+    fc->queue = queue;
+    if (initial_path)
+        fc->initial_path = al_path_clone(initial_path);
+    if (patterns)
+        fc->patterns = strdup(patterns);
+    fc->mode = mode;
+
+    al_register_event_source(queue, fc->event_source);
+    fc->thread = al_create_thread(gtk_thread, NULL);
+    al_start_thread(global_file_chooser->thread);
+    return true;
+}
+
+/* Function: al_finalize_native_file_dialog
+ * 
+ * Parameters:
+ * - queue: Pass the same queue you passed when spawning the dialog.
+ * - event: The notification event.
+ * - pathes: An array of pathes which will receive the selection.
+ * - n: Maximum number of pathes to retrieve. Usually you will use 1
+ *   here, unless the ALLEGRO_FILECHOOSER_MULTIPLE flags was used.
+ * 
+ * Returns:
+ * 0 if the dialog was cancelled by the user, else the number of
+ * stored pathes (at most n).
+ */
+int al_finalize_native_file_dialog(ALLEGRO_EVENT_QUEUE *queue,
+    ALLEGRO_EVENT *event, ALLEGRO_PATH *pathes[], int n)
+{
+    ALLEGRO_NATIVE_FILE_CHOOSER *fc = global_file_chooser;
+    int count = 0;
+    ASSERT(event->user.type == ALLEGRO_EVENT_NATIVE_FILE_CHOOSER);
+    ASSERT(queue == global_file_chooser->queue);
+
+    if (fc->pathes) {
+        int i;
+        for (i = 0; i < fc->n; i++) {
+            if (i < n) {
+                count++;
+                pathes[i] = fc->pathes[i];
+            }
+            else
+                al_path_free(fc->pathes[i]);
+        }
+    }
+
+    al_unregister_event_source(queue, fc->event_source);
+    al_destroy_user_event_source(fc->event_source);
+    al_destroy_thread(fc->thread);
+    if (fc->initial_path)
+        al_path_free(fc->initial_path);
+    if (fc->patterns)
+        free(fc->patterns);
+    _AL_FREE(fc);
+    global_file_chooser = NULL;
+    return count;
+}
Index: addons/native_dialog/allegro5/a5_native_dialog.h
===================================================================
--- addons/native_dialog/allegro5/a5_native_dialog.h	(revision 0)
+++ addons/native_dialog/allegro5/a5_native_dialog.h	(revision 0)
@@ -0,0 +1,51 @@
+#include "allegro5/allegro5.h"
+
+#ifdef __cplusplus
+   extern "C" {
+#endif
+
+#if (defined ALLEGRO_MINGW32) || (defined ALLEGRO_MSVC) || (defined ALLEGRO_BCC32)
+   #ifndef ALLEGRO_STATICLINK
+      #ifdef A5_DIALOG_SRC
+         #define _A5_DIALOG_DLL __declspec(dllexport)
+      #else
+         #define _A5_DIALOG_DLL __declspec(dllimport)
+      #endif
+   #else
+      #define _A5_DIALOG_DLL
+   #endif
+#endif
+
+#if defined ALLEGRO_MSVC
+   #define A5_DIALOG_FUNC(type, name, args)      _A5_DIALOG_DLL type __cdecl name args
+#elif defined ALLEGRO_MINGW32
+   #define A5_DIALOG_FUNC(type, name, args)      extern type name args
+#elif defined ALLEGRO_BCC32
+   #define A5_DIALOG_FUNC(type, name, args)      extern _A5_DIALOG_DLL type name args
+#else
+   #define A5_DIALOG_FUNC      AL_FUNC
+#endif
+
+A5_DIALOG_FUNC(bool, al_spawn_native_file_dialog,
+    (ALLEGRO_EVENT_QUEUE *queue, ALLEGRO_PATH const *initial_path,
+    char const *patterns, int mode));
+A5_DIALOG_FUNC(int, al_finalize_native_file_dialog,
+    (ALLEGRO_EVENT_QUEUE *queue, ALLEGRO_EVENT *event,
+    ALLEGRO_PATH *pathes[], int n));
+
+#define ALLEGRO_FILECHOOSER_MUST_EXIST 1
+#define ALLEGRO_FILECHOOSER_SAVE 2
+#define ALLEGRO_FILECHOOSER_FOLDER 4
+#define ALLEGRO_FILECHOOSER_PICTURES 8
+#define ALLEGRO_FILECHOOSER_SHOW_HIDDEN 16
+#define ALLEGRO_FILECHOOSER_MULTIPLE 32
+
+// FIXME: 1024 is the first user event. There needs to be an area
+// reserved for addons. In fact, we might want to have a function
+// al_get_new_user_event_number() which returns the next unused number
+// at runtime, and each addon could allocate their own dynamic numbers.
+#define ALLEGRO_EVENT_NATIVE_FILE_CHOOSER 65536
+
+#ifdef __cplusplus
+   }
+#endif
Index: addons/native_dialog/allegro5/internal/aintern_native_dialog_cfg.h.cmake
===================================================================
--- addons/native_dialog/allegro5/internal/aintern_native_dialog_cfg.h.cmake	(revision 0)
+++ addons/native_dialog/allegro5/internal/aintern_native_dialog_cfg.h.cmake	(revision 0)
@@ -0,0 +1 @@
+
Index: addons/native_dialog/allegro5/internal/aintern_native_dialog.h
===================================================================
--- addons/native_dialog/allegro5/internal/aintern_native_dialog.h	(revision 0)
+++ addons/native_dialog/allegro5/internal/aintern_native_dialog.h	(revision 0)
@@ -0,0 +1 @@
+
Index: addons/native_dialog/CMakeLists.txt
===================================================================
--- addons/native_dialog/CMakeLists.txt	(revision 0)
+++ addons/native_dialog/CMakeLists.txt	(revision 0)
@@ -0,0 +1,28 @@
+pkg_check_modules(GTK gtk+-2.0)
+
+if(GTK_FOUND)
+
+set(SOURCES gtk.c)
+set(INCLUDE_FILES allegro5/a5_native_dialog.h)
+
+configure_file(
+    allegro5/internal/aintern_native_dialog_cfg.h.cmake
+    ${CMAKE_BINARY_DIR}/include/allegro5/internal/aintern_native_dialog_cfg.h
+    )
+
+include_directories(${GTK_INCLUDE_DIRS})
+
+add_our_library(a5_dialog -${ALLEGRO_VERSION}
+    "${SOURCES}"
+    "-DA5_NATIVE_DIALOG_SRC"
+    "${GTK_LIBRARIES}"
+    )
+
+set(NATIVE_DIALOG_LINK_WITH a5_dialog PARENT_SCOPE)
+
+install(FILES ${INCLUDE_FILES}
+        DESTINATION include/allegro5
+        )
+
+endif(GTK_FOUND)
+
Index: addons/CMakeLists.txt
===================================================================
--- addons/CMakeLists.txt	(revision 11638)
+++ addons/CMakeLists.txt	(working copy)
@@ -48,7 +48,6 @@
     set(COLOR_LINK_WITH ${COLOR_LINK_WITH} PARENT_SCOPE)
 endif(WANT_COLOR)
 
-
 if(WANT_MEMFILE)
     add_subdirectory(memfile)
     set(SUPPORT_MEMFILE 1 PARENT_SCOPE)
@@ -61,5 +60,11 @@
     set(PRIMITIVES_LINK_WITH ${PRIMITIVES_LINK_WITH} PARENT_SCOPE)
 endif(WANT_PRIMITIVES)
 
+if(WANT_NATIVE_DIALOG)
+    add_subdirectory(native_dialog)
+    set(SUPPORT_NATIVE_DIALOG 1 PARENT_SCOPE)
+    set(NATIVE_DIALOG_LINK_WITH ${NATIVE_DIALOG_LINK_WITH} PARENT_SCOPE)
+endif(WANT_NATIVE_DIALOG)
+
 #-----------------------------------------------------------------------------#
 # vi: set ts=8 sts=4 sw=4 et:
Index: CMakeLists.txt
===================================================================
--- CMakeLists.txt	(revision 11638)
+++ CMakeLists.txt	(working copy)
@@ -86,6 +86,7 @@
 option(WANT_COLOR "Enable color addon" on)
 option(WANT_MEMFILE "Enable memfile addon" on)
 option(WANT_PRIMITIVES "Enable primitives addon" on)
+option(WANT_NATIVE_DIALOG "Enable native dialog addon" on)
 
 option(WANT_ALSA "Enable ALSA digital audio driver (Unix)" on)
 option(WANT_OSS "Enable OSS digital audio driver (Unix)" on)
Index: examples/ex_native_filechooser.c
===================================================================
--- examples/ex_native_filechooser.c	(revision 0)
+++ examples/ex_native_filechooser.c	(revision 0)
@@ -0,0 +1,99 @@
+#include <stdio.h>
+#include <allegro5/allegro5.h>
+#include <allegro5/a5_native_dialog.h>
+#include <allegro5/a5_font.h>
+#include <allegro5/a5_color.h>
+
+int main(void)
+{
+    ALLEGRO_DISPLAY *display;
+    ALLEGRO_TIMER *timer;
+    ALLEGRO_EVENT_QUEUE *queue;
+    ALLEGRO_FONT *font;
+    bool redraw = true;
+    ALLEGRO_COLOR background, active, inactive, info;
+    bool dialog_shown = false;
+    ALLEGRO_PATH *last_path = NULL;
+    ALLEGRO_PATH *result[25];
+    int count = 0;
+
+    al_init();
+    al_font_init();
+
+    background = al_color_name("white");
+    active = al_color_name("black");
+    inactive = al_color_name("gray");
+    info = al_color_name("red");
+
+    font = al_font_load_font("data/fixed_font.tga", 0);
+
+    al_install_mouse();
+    al_install_keyboard();
+
+    display = al_create_display(640, 480);
+    if (!display) {
+       TRACE("Error creating display\n");
+       return 1;
+    }
+
+    timer = al_install_timer(1.0 / 30);
+    queue = al_create_event_queue();
+    al_register_event_source(queue, (void *)al_get_keyboard());
+    al_register_event_source(queue, (void *)al_get_mouse());
+    al_register_event_source(queue, (void *)display);
+    al_register_event_source(queue, (void *)timer);
+    al_start_timer(timer);
+
+    while (1) {
+        ALLEGRO_EVENT event;
+        al_wait_for_event(queue, &event);
+        if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
+            break;
+        if (event.type == ALLEGRO_EVENT_KEY_DOWN) {
+            if (event.keyboard.keycode == ALLEGRO_KEY_ESCAPE)
+                break;
+        }
+        if (event.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN) {
+            dialog_shown = al_spawn_native_file_dialog(queue, last_path,
+                NULL, ALLEGRO_FILECHOOSER_MULTIPLE);
+        }
+        if (event.type == ALLEGRO_EVENT_NATIVE_FILE_CHOOSER) {
+            count = al_finalize_native_file_dialog(
+                queue, &event, result, 25);
+            dialog_shown = false;
+            if (last_path)
+                al_path_free(last_path);
+            last_path = NULL;
+            if (count)
+                last_path = result[0];
+        }
+        if (event.type == ALLEGRO_EVENT_TIMER)
+            redraw = true;
+            
+        if (redraw && al_event_queue_is_empty(queue)) {
+            float x = al_get_display_width() / 2;
+            float y = 0;
+            int i;
+            int th = al_font_text_height(font);
+            redraw = false;
+            al_clear(background);
+            al_set_blender(ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA,
+                dialog_shown ? inactive : active);
+            al_font_textprintf_centre(font, x, y, "Open");
+            y = al_get_display_height() / 2 - (count * th) / 2;
+            for (i = 0; i < count; i++) {
+                char name[PATH_MAX];
+                al_path_to_string(result[i], name, sizeof name, '/');
+                al_set_blender(ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA,
+                    info);
+                al_font_textprintf_centre(font, x, y + i * th, name);
+            }
+            al_flip_display();
+        }
+    }
+
+    return 0;
+}
+END_OF_MAIN()
+
+/* vim: set sts=4 sw=4 et: */
Index: examples/CMakeLists.txt
===================================================================
--- examples/CMakeLists.txt	(revision 11638)
+++ examples/CMakeLists.txt	(working copy)
@@ -7,6 +7,7 @@
     ../addons/color
     ../addons/memfile
     ../addons/primitives
+    ../addons/native_dialog
     )
 
 if(SUPPORT_FONT)
@@ -110,6 +111,11 @@
         ${IIO_LINK_WITH})
 endif(SUPPORT_PRIMITIVES AND SUPPORT_FONT)
 
+if(SUPPORT_NATIVE_DIALOG AND SUPPORT_FONT AND SUPPORT_COLOR)
+    example(ex_native_filechooser ${NATIVE_DIALOG_LINK_WITH}
+        ${FONT_LINK_WITH} ${COLOR_LINK_WITH})
+endif()
+
 copy_data_dir_to_build(copy_example_data data)
 
 #-----------------------------------------------------------------------------#


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/