[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)
#-----------------------------------------------------------------------------#