From a3f2ec0a5b00a3898e0ae006d142e13a88513416 Mon Sep 17 00:00:00 2001 From: Pavel Sountsov Date: Sat, 22 Jun 2024 12:48:48 -0700 Subject: [PATCH 1/2] Add al_ref_info to convert ALLEGRO_USTR_INFO to ALLEGRO_USTR. --- docs/src/refman/utf8.txt | 15 +++++++++++++-- include/allegro5/utf8.h | 1 + src/utf8.c | 7 +++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/src/refman/utf8.txt b/docs/src/refman/utf8.txt index b600f024ee..58c455903f 100644 --- a/docs/src/refman/utf8.txt +++ b/docs/src/refman/utf8.txt @@ -97,9 +97,10 @@ NULs. ### API: ALLEGRO_USTR_INFO A type that holds additional information for an [ALLEGRO_USTR] that references -an external memory buffer. +an external memory buffer. You can convert it back to [ALLEGRO_USTR] via +[al_ref_info]. -See also: [al_ref_cstr], [al_ref_buffer] and [al_ref_ustr]. +See also: [al_ref_cstr], [al_ref_buffer], [al_ref_info] and [al_ref_ustr]. ## Creating and destroying strings @@ -269,6 +270,16 @@ to find the byte offsets. See also: [al_ref_cstr], [al_ref_buffer] +### API: al_ref_info + +Create a read-only string that references the storage of another [ALLEGRO_USTR] +string that has already been stored in the [ALLEGRO_USTR_INFO] structure. + +The string is valid until the underlying string is modified or destroyed. + +See also: [al_ref_cstr], [al_ref_buffer], [al_ref_ustr] + + ## Sizes and offsets ### API: al_ustr_size diff --git a/include/allegro5/utf8.h b/include/allegro5/utf8.h index f0459dac04..cb94a77bb7 100644 --- a/include/allegro5/utf8.h +++ b/include/allegro5/utf8.h @@ -45,6 +45,7 @@ AL_FUNC(const ALLEGRO_USTR *, al_ref_buffer, (ALLEGRO_USTR_INFO *info, const cha size_t size)); AL_FUNC(const ALLEGRO_USTR *, al_ref_ustr, (ALLEGRO_USTR_INFO *info, const ALLEGRO_USTR *us, int start_pos, int end_pos)); +AL_FUNC(const ALLEGRO_USTR *, al_ref_info, (const ALLEGRO_USTR_INFO *info)); /* Sizes and offsets */ AL_FUNC(size_t, al_ustr_size, (const ALLEGRO_USTR *us)); diff --git a/src/utf8.c b/src/utf8.c index 1a6e55d83b..5007c555e7 100644 --- a/src/utf8.c +++ b/src/utf8.c @@ -192,6 +192,13 @@ const ALLEGRO_USTR *al_ref_ustr(ALLEGRO_USTR_INFO *info, const ALLEGRO_USTR *us, } +/* Function: al_ref_info + */ +const ALLEGRO_USTR *al_ref_info(const ALLEGRO_USTR_INFO *info) +{ + return info; +} + /* Function: al_ustr_size */ size_t al_ustr_size(const ALLEGRO_USTR *us) From ce057526ce6e907aa6b6f6df135e29a2cf67c146 Mon Sep 17 00:00:00 2001 From: Pavel Sountsov Date: Sat, 22 Jun 2024 12:49:05 -0700 Subject: [PATCH 2/2] Improve the patterns arg in al_create_native_file_dialog This adds: - Support for multiple pattern sets (Windows/Linux) - Supports for custom pattern set descriptions (Windows/Linux) - Support for MIME types on MacOS Also: - Remove the implicit catch-all pattern on Windows (the "All files *.*"), it was inconsistent with other platforms. - Make MacOS do a better job at extracting the file extension - Improve documentation overall --- .../allegro5/internal/aintern_native_dialog.h | 22 ++- addons/native_dialog/android_dialog.c | 25 +++- addons/native_dialog/dialog.c | 117 ++++++++++++++- addons/native_dialog/gtk_filesel.c | 48 +++---- addons/native_dialog/osx_dialog.m | 135 +++++++++++------- addons/native_dialog/win_dialog.c | 109 +++++++------- .../org/liballeg/android/AllegroDialog.java | 11 +- docs/src/refman/native_dialog.txt | 52 ++++--- 8 files changed, 354 insertions(+), 165 deletions(-) diff --git a/addons/native_dialog/allegro5/internal/aintern_native_dialog.h b/addons/native_dialog/allegro5/internal/aintern_native_dialog.h index 6dc5c4f143..2dd7c1bc2b 100644 --- a/addons/native_dialog/allegro5/internal/aintern_native_dialog.h +++ b/addons/native_dialog/allegro5/internal/aintern_native_dialog.h @@ -5,6 +5,19 @@ #include "allegro5/internal/aintern_vector.h" #include "allegro5/internal/aintern_native_dialog_cfg.h" +typedef struct +{ + ALLEGRO_USTR_INFO info; + bool is_mime; + bool is_catchall; +} _AL_PATTERN; + +typedef struct +{ + ALLEGRO_USTR_INFO desc; + _AL_VECTOR patterns_vec; +} _AL_PATTERNS_AND_DESC; + typedef struct ALLEGRO_NATIVE_DIALOG ALLEGRO_NATIVE_DIALOG; /* We could use different structs for the different dialogs. But why @@ -19,7 +32,10 @@ struct ALLEGRO_NATIVE_DIALOG ALLEGRO_PATH *fc_initial_path; size_t fc_path_count; ALLEGRO_PATH **fc_paths; - ALLEGRO_USTR *fc_patterns; + /* Vector of _AL_PATTERNS_AND_DESC, substrings index into fc_patterns_ustr. + */ + _AL_VECTOR fc_patterns; + ALLEGRO_USTR *fc_patterns_ustr; /* Only used by message box. */ ALLEGRO_USTR *mb_heading; @@ -42,7 +58,7 @@ struct ALLEGRO_NATIVE_DIALOG bool is_active; void *window; void *async_queue; - + _AL_LIST_ITEM *dtor_item; }; @@ -113,7 +129,7 @@ extern bool _al_walk_over_menu(ALLEGRO_MENU *menu, bool (*proc) _AL_MENU_ID *_al_find_parent_menu_by_id(ALLEGRO_DISPLAY *display, uint16_t unique_id); bool _al_find_menu_item_unique(ALLEGRO_MENU *haystack, uint16_t unique_id, ALLEGRO_MENU **menu, int *index); - + /* Platform Specific Functions * --------------------------- * Each of these should return true if successful. If at all possible, they diff --git a/addons/native_dialog/android_dialog.c b/addons/native_dialog/android_dialog.c index 5bd7aec472..10939bc3fc 100644 --- a/addons/native_dialog/android_dialog.c +++ b/addons/native_dialog/android_dialog.c @@ -90,7 +90,30 @@ bool _al_show_native_file_dialog(ALLEGRO_DISPLAY *display, ALLEGRO_NATIVE_DIALOG /* open the file chooser */ ALLEGRO_DEBUG("waiting for the file chooser"); - bool ret = open_file_chooser(fd->flags, al_cstr(fd->fc_patterns), initial_path, &fd->fc_paths, &fd->fc_path_count); + ALLEGRO_USTR *mime_patterns = al_ustr_new(""); + bool first = true; + bool any_catchalls = false; + for (size_t i = 0; i < _al_vector_size(&fd->fc_patterns); i++) { + _AL_PATTERNS_AND_DESC *patterns_and_desc = _al_vector_ref(&fd->fc_patterns, (int)i); + for (size_t j = 0; j < _al_vector_size(&patterns_and_desc->patterns_vec); j++) { + _AL_PATTERN *pattern = _al_vector_ref(&patterns_and_desc->patterns_vec, (int)j); + if (pattern->is_catchall) { + any_catchalls = true; + break; + } + if (pattern->is_mime) + { + if (!first) + al_ustr_append_chr(mime_patterns, ';'); + first = false; + al_ustr_append(mime_patterns, al_ref_info(&pattern->info)); + } + } + } + if (any_catchalls) + al_ustr_truncate(mime_patterns, 0); + bool ret = open_file_chooser(fd->flags, al_cstr(mime_patterns), initial_path, &fd->fc_paths, &fd->fc_path_count); + al_ustr_free(mime_patterns); ALLEGRO_DEBUG("done waiting for the file chooser"); /* ensure predictable behavior */ diff --git a/addons/native_dialog/dialog.c b/addons/native_dialog/dialog.c index d89a722f24..85142c1611 100644 --- a/addons/native_dialog/dialog.c +++ b/addons/native_dialog/dialog.c @@ -10,6 +10,9 @@ ALLEGRO_DEBUG_CHANNEL("native_dialog") static bool inited_addon = false; +static _AL_VECTOR make_patterns_and_desc_vec(const ALLEGRO_USTR* patterns_ustr); +static void free_patterns_and_desc_vec(_AL_VECTOR *patterns_and_desc_vec); + /* Function: al_init_native_dialog_addon */ bool al_init_native_dialog_addon(void) @@ -60,7 +63,8 @@ ALLEGRO_FILECHOOSER *al_create_native_file_dialog( fc->fc_initial_path = al_create_path(initial_path); } fc->title = al_ustr_new(title); - fc->fc_patterns = al_ustr_new(patterns); + fc->fc_patterns_ustr = al_ustr_new(patterns); + fc->fc_patterns = make_patterns_and_desc_vec(fc->fc_patterns_ustr); fc->flags = mode; fc->dtor_item = _al_register_destructor(_al_dtor_list, "native_dialog", fc, @@ -115,7 +119,8 @@ void al_destroy_native_file_dialog(ALLEGRO_FILECHOOSER *dialog) al_destroy_path(fd->fc_paths[i]); } al_free(fd->fc_paths); - al_ustr_free(fd->fc_patterns); + free_patterns_and_desc_vec(&fd->fc_patterns); + al_ustr_free(fd->fc_patterns_ustr); al_free(fd); } @@ -165,4 +170,112 @@ uint32_t al_get_allegro_native_dialog_version(void) } +static _AL_VECTOR split_patterns(const ALLEGRO_USTR* ustr) +{ + int pattern_start = 0; + int cur_pos = 0; + bool is_mime = false; + bool is_catchall = true; + _AL_VECTOR patterns_vec; + _al_vector_init(&patterns_vec, sizeof(_AL_PATTERN)); + /* This does a straightforward split on semicolons. We check for MIME types + * and catchalls, as some backends care about this. + */ + while (true) { + int32_t c = al_ustr_get(ustr, cur_pos); + if (c == -1 || c == ';') { + ALLEGRO_USTR_INFO info; + const ALLEGRO_USTR *pattern_ustr = al_ref_buffer(&info, + al_cstr(ustr) + pattern_start, cur_pos - pattern_start); + if (al_ustr_length(pattern_ustr) > 0) { + _AL_PATTERN pattern = { + .info = info, .is_mime = is_mime, .is_catchall = is_catchall + }; + *((_AL_PATTERN*)_al_vector_alloc_back(&patterns_vec)) = pattern; + } + + is_mime = false; + is_catchall = true; + pattern_start = cur_pos + 1; + } + else if (c == '/') { + is_mime = true; + is_catchall = false; + } + else if (c != '*' && c != '.') { + is_catchall = false; + } + if (c == -1) { + break; + } + al_ustr_next(ustr, &cur_pos); + } + return patterns_vec; +} + + +static _AL_VECTOR make_patterns_and_desc_vec(const ALLEGRO_USTR* patterns_ustr) +{ + _AL_VECTOR patterns_and_desc_vec; + _al_vector_init(&patterns_and_desc_vec, sizeof(_AL_PATTERNS_AND_DESC)); + + if (al_ustr_length(patterns_ustr) == 0) + return patterns_and_desc_vec; + + int line_start = 0; + int chunk_start = 0; + int cur_pos = 0; + /* Split by newlines + spaces simultaneously. Chunks are separated by + * spaces, and the final chunk is interpreted as the actual patterns, while + * the rest of the line is the "description". + */ + while (true) { + int32_t c = al_ustr_get(patterns_ustr, cur_pos); + if (c == ' ') { + chunk_start = cur_pos + 1; + } + else if (c == '\n' || c == -1) { + ALLEGRO_USTR_INFO desc_info, real_patterns_info; + const ALLEGRO_USTR *ustr; + /* Strip trailing whitespace. */ + int desc_end = chunk_start - 1; + for (; desc_end >= line_start; desc_end--) { + if (al_ustr_get(patterns_ustr, desc_end) != ' ') + break; + } + al_ref_ustr(&desc_info, patterns_ustr, line_start, desc_end + 1); + ustr = al_ref_ustr(&real_patterns_info, patterns_ustr, chunk_start, cur_pos); + + _AL_VECTOR patterns_vec = split_patterns(ustr); + if (_al_vector_size(&patterns_vec) > 0) { + _AL_PATTERNS_AND_DESC patterns_and_desc = { + .desc = desc_info, + .patterns_vec = patterns_vec + }; + *((_AL_PATTERNS_AND_DESC*)_al_vector_alloc_back(&patterns_and_desc_vec)) = patterns_and_desc; + } + + chunk_start = cur_pos + 1; + line_start = cur_pos + 1; + } + if (c == -1) { + break; + } + al_ustr_next(patterns_ustr, &cur_pos); + } + + return patterns_and_desc_vec; +} + + +static void free_patterns_and_desc_vec(_AL_VECTOR *patterns_and_desc_vec) +{ + for (size_t i = 0; i < _al_vector_size(patterns_and_desc_vec); i++) { + _AL_PATTERNS_AND_DESC *patterns_and_desc = + (_AL_PATTERNS_AND_DESC*)_al_vector_ref(patterns_and_desc_vec, i); + _al_vector_free(&patterns_and_desc->patterns_vec); + } + _al_vector_free(patterns_and_desc_vec); +} + /* vim: set sts=3 sw=3 et: */ diff --git a/addons/native_dialog/gtk_filesel.c b/addons/native_dialog/gtk_filesel.c index 4976736a0e..941a21c345 100644 --- a/addons/native_dialog/gtk_filesel.c +++ b/addons/native_dialog/gtk_filesel.c @@ -98,36 +98,30 @@ static gboolean create_gtk_file_dialog(gpointer data) if (fd->flags & ALLEGRO_FILECHOOSER_MULTIPLE) gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(window), true); - /* FIXME: Move all this filter parsing stuff into a common file. */ - if (al_ustr_size(fd->fc_patterns) > 0) { + for (size_t i = 0; i < _al_vector_size(&fd->fc_patterns); i++) + { + _AL_PATTERNS_AND_DESC *patterns_and_desc = _al_vector_ref(&fd->fc_patterns, i); + const ALLEGRO_USTR *desc = al_ref_info(&patterns_and_desc->desc); GtkFileFilter* filter = gtk_file_filter_new(); - int start = 0; - int end = 0; - bool is_mime_type = false; - while (true) { - int32_t c = al_ustr_get(fd->fc_patterns, end); - if (c < 0 || c == ';') { - if (end - start > 0) { - ALLEGRO_USTR* pattern = al_ustr_dup_substr(fd->fc_patterns, start, end); - if (is_mime_type) { - gtk_file_filter_add_mime_type(filter, al_cstr(pattern)); - } - else { - gtk_file_filter_add_pattern(filter, al_cstr(pattern)); - } - al_ustr_free(pattern); - } - start = end + 1; - is_mime_type = false; + if (al_ustr_size(desc) > 0) { + char *cstr = al_cstr_dup(desc); + gtk_file_filter_set_name(filter, cstr); + al_free(cstr); + } + else { + gtk_file_filter_set_name(filter, "All supported files"); + } + for (size_t j = 0; j < _al_vector_size(&patterns_and_desc->patterns_vec); j++) { + _AL_PATTERN *pattern = _al_vector_ref(&patterns_and_desc->patterns_vec, j); + char *cstr = al_cstr_dup(al_ref_info(&pattern->info)); + if (pattern->is_mime) { + gtk_file_filter_add_mime_type(filter, cstr); + } + else { + gtk_file_filter_add_pattern(filter, cstr); } - if (c == '/') - is_mime_type = true; - if (c < 0) - break; - end += al_utf8_width(c); + al_free(cstr); } - - gtk_file_filter_set_name(filter, "All supported files"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(window), filter); } diff --git a/addons/native_dialog/osx_dialog.m b/addons/native_dialog/osx_dialog.m index ab60215213..0e326ed33d 100644 --- a/addons/native_dialog/osx_dialog.m +++ b/addons/native_dialog/osx_dialog.m @@ -1,6 +1,9 @@ #import #import #import +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1100 +#include +#endif #include "allegro5/allegro.h" #include "allegro5/allegro_native_dialog.h" #include "allegro5/internal/aintern_native_dialog.h" @@ -17,36 +20,53 @@ void _al_shutdown_native_dialog_addon(void) } #pragma mark File Dialog -static NSArray * remove_mime_types(NSArray * array) +static NSArray * get_filter_array(const _AL_VECTOR *patterns) { - NSMutableArray * work_array = [array mutableCopy]; + NSMutableArray *filter_array = [[NSMutableArray alloc] init]; - for(NSInteger i = work_array.count - 1; i >= 0; i--){ - if([[work_array objectAtIndex: i] rangeOfString:@"/"].location != NSNotFound){ - [work_array removeObjectAtIndex: i]; + bool any_catchalls = false; + for (size_t i = 0; i < _al_vector_size(patterns); i++) { + _AL_PATTERNS_AND_DESC *patterns_and_desc = _al_vector_ref(patterns, (int)i); + for (size_t j = 0; j < _al_vector_size(&patterns_and_desc->patterns_vec); j++) { + _AL_PATTERN *pattern = _al_vector_ref(&patterns_and_desc->patterns_vec, (int)j); + if (pattern->is_catchall) { + any_catchalls = true; + break; + } + char *cstr = al_cstr_dup(al_ref_info(&pattern->info)); + NSString *filter_text = [NSString stringWithUTF8String: cstr]; + al_free(cstr); + if (!pattern->is_mime) { + /* MacOS expects extensions, so make an attempt to extract them. */ + NSArray *parts = [filter_text componentsSeparatedByString: @"."]; + size_t num_parts = parts.count; + if (num_parts <= 1) + continue; + /* Extensions with dots did not work for me, so just grab the last component. */ + parts = [parts subarrayWithRange:NSMakeRange(num_parts - 1, 1)]; + filter_text = [parts componentsJoinedByString: @"."]; + } +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1100 + UTType *type; + if (pattern->is_mime) + type = [UTType typeWithMIMEType:filter_text]; + else + type = [UTType typeWithFilenameExtension:filter_text]; + if (type != nil) + [filter_array addObject: type]; +#else + if (pattern->is_mime) { + continue; + } + [filter_array addObject: filter_text]; +#endif } } - return [work_array copy]; -} - -static NSArray * get_filter_array(ALLEGRO_USTR * patterns) -{ - NSMutableString *filter_text; - NSArray *filter_array = nil; - - filter_text = [NSMutableString stringWithUTF8String: al_cstr(patterns)]; - [filter_text replaceOccurrencesOfString:@"*" - withString:@"" - options:0 - range:NSMakeRange(0, filter_text.length)]; - [filter_text replaceOccurrencesOfString:@"." - withString:@"" - options:0 - range:NSMakeRange(0, filter_text.length)]; - if (filter_text.length > 0) { - filter_array = [filter_text componentsSeparatedByString: @";"]; - filter_array = remove_mime_types(filter_array); + + if (any_catchalls) { + filter_array = [[NSMutableArray alloc] init]; } + return filter_array; } @@ -58,13 +78,13 @@ bool _al_show_native_file_dialog(ALLEGRO_DISPLAY *display, int mode = fd->flags; NSString *filename; NSURL *directory; - + /* Set initial directory to pass to the file selector */ if (fd->fc_initial_path) { ALLEGRO_PATH *initial_directory = al_clone_path(fd->fc_initial_path); /* Strip filename from path */ al_set_path_filename(initial_directory, NULL); - + /* Convert path and filename to NSString objects */ directory = [NSURL fileURLWithPath: [NSString stringWithUTF8String: al_path_cstr(initial_directory, '/')] isDirectory: YES]; @@ -74,25 +94,30 @@ bool _al_show_native_file_dialog(ALLEGRO_DISPLAY *display, directory = nil; filename = nil; } - dispatch_sync(dispatch_get_main_queue(), ^{ + dispatch_sync(dispatch_get_main_queue(), ^{ /* We need slightly different code for SAVE and LOAD dialog boxes, which * are handled by slightly different classes. */ if (mode & ALLEGRO_FILECHOOSER_SAVE) { // Save dialog NSSavePanel *panel = [NSSavePanel savePanel]; NSArray *filter_array; - + /* Set file save dialog box options */ [panel setCanCreateDirectories: YES]; [panel setCanSelectHiddenExtension: YES]; [panel setAllowsOtherFileTypes: YES]; + [panel setExtensionHidden: NO]; if (filename) { [panel setNameFieldStringValue:filename]; } - if (fd->fc_patterns) { - filter_array = get_filter_array(fd->fc_patterns); + if (_al_vector_size(&fd->fc_patterns) > 0) { + filter_array = get_filter_array(&fd->fc_patterns); if (filter_array && [filter_array count] > 0) { +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1100 + [panel setAllowedContentTypes:filter_array]; +#else [panel setAllowedFileTypes:filter_array]; +#endif } } [panel setDirectoryURL: directory]; @@ -111,7 +136,7 @@ bool _al_show_native_file_dialog(ALLEGRO_DISPLAY *display, } else { // Open dialog NSOpenPanel *panel = [NSOpenPanel openPanel]; NSArray *filter_array; - + /* Set file selection box options */ if (mode & ALLEGRO_FILECHOOSER_FOLDER) { [panel setCanChooseFiles: NO]; @@ -120,7 +145,7 @@ bool _al_show_native_file_dialog(ALLEGRO_DISPLAY *display, [panel setCanChooseFiles: YES]; [panel setCanChooseDirectories: NO]; } - + [panel setResolvesAliases:YES]; if (mode & ALLEGRO_FILECHOOSER_MULTIPLE) [panel setAllowsMultipleSelection: YES]; @@ -130,10 +155,14 @@ bool _al_show_native_file_dialog(ALLEGRO_DISPLAY *display, if (filename) { [panel setNameFieldStringValue:filename]; } - if (fd->fc_patterns) { - filter_array = get_filter_array(fd->fc_patterns); + if (_al_vector_size(&fd->fc_patterns) > 0) { + filter_array = get_filter_array(&fd->fc_patterns); if (filter_array && [filter_array count] > 0) { +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1100 + [panel setAllowedContentTypes:filter_array]; +#else [panel setAllowedFileTypes:filter_array]; +#endif } } /* Open dialog box */ @@ -156,9 +185,9 @@ bool _al_show_native_file_dialog(ALLEGRO_DISPLAY *display, }); _al_osx_clear_mouse_state(); - + [pool release]; - + return true; } @@ -172,7 +201,7 @@ int _al_show_native_message_box(ALLEGRO_DISPLAY *display, NSString* button_text; unsigned int i; NSAlert* box = [[NSAlert alloc] init]; - + if (fd->mb_buttons == NULL) { button_text = @"OK"; if (fd->flags & ALLEGRO_MESSAGEBOX_YES_NO) button_text = @"Yes|No"; @@ -181,7 +210,7 @@ int _al_show_native_message_box(ALLEGRO_DISPLAY *display, else { button_text = [NSString stringWithUTF8String: al_cstr(fd->mb_buttons)]; } - + NSArray* buttons = [button_text componentsSeparatedByString: @"|"]; [box setMessageText:[NSString stringWithUTF8String: al_cstr(fd->title)]]; [box setInformativeText:[NSString stringWithUTF8String: al_cstr(fd->mb_text)]]; @@ -189,7 +218,7 @@ int _al_show_native_message_box(ALLEGRO_DISPLAY *display, [[box window] setLevel: NSFloatingWindowLevel]; for (i = 0; i < [buttons count]; ++i) [box addButtonWithTitle: [buttons objectAtIndex: i]]; - + int retval = [box runModal]; fd->mb_pressed_button = retval + 1 - NSAlertFirstButtonReturn; [box release]; @@ -272,13 +301,13 @@ bool _al_open_native_text_log(ALLEGRO_NATIVE_DIALOG *textlog) unsigned int mask = NSTitledWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask; if (!(textlog->flags & ALLEGRO_TEXTLOG_NO_CLOSE)) mask |= NSClosableWindowMask; - + if ((adapter >= 0) && (adapter < al_get_num_video_adapters())) { screen = [[NSScreen screens] objectAtIndex: adapter]; } else { screen = [NSScreen mainScreen]; } - + dispatch_sync(dispatch_get_main_queue(), ^{ NSWindow *win = [[NSWindow alloc] initWithContentRect: rect styleMask: mask @@ -293,10 +322,10 @@ bool _al_open_native_text_log(ALLEGRO_NATIVE_DIALOG *textlog) [scrollView setHasVerticalScroller: YES]; [scrollView setAutohidesScrollers: YES]; [scrollView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable]; - + [[scrollView contentView] setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable]; [[scrollView contentView] setAutoresizesSubviews: YES]; - + NSRect framerect = [[scrollView contentView] frame]; ALLEGLogView *view = [[ALLEGLogView alloc] initWithFrame: framerect]; view->textlog = textlog; @@ -311,13 +340,13 @@ bool _al_open_native_text_log(ALLEGRO_NATIVE_DIALOG *textlog) } [view setEditable: NO]; [scrollView setDocumentView: view]; - + [[win contentView] addSubview: scrollView]; [scrollView release]; - + [win setDelegate: view]; [win orderFront: nil]; - + /* Save handles for future use. */ textlog->window = win; // Non-owning reference textlog->tl_textview = view; // Non-owning reference @@ -354,7 +383,7 @@ void _al_append_native_text_log(ALLEGRO_NATIVE_DIALOG *textlog) [view performSelectorOnMainThread:@selector(appendText:) withObject:text waitUntilDone:NO]; - + al_ustr_truncate(textlog->tl_pending_text, 0); } } @@ -377,7 +406,7 @@ -(void) setMenu: (NSMenu*) menu forWindow:(NSWindow*) window; @end /* This class represents a menu item target. There is one of these - * for each NSMenu and it handles all the items in that menu. It + * for each NSMenu and it handles all the items in that menu. It * maintains the correspondence between ALLEGRO_MENU_ITEMs and * NSMenuItems, taking into account the 'App menu' (the one with * the app's name in bold) which appears by convention on OS X. @@ -472,7 +501,7 @@ bool _al_show_popup_menu(ALLEGRO_DISPLAY *display, ALLEGRO_MENU *amenu) (void) display; ALLEGMenuTarget* target = [ALLEGMenuTarget targetForMenu: amenu]; [target performSelectorOnMainThread:@selector(showPopup) withObject:nil waitUntilDone:NO]; - + return true; } @@ -509,7 +538,7 @@ -(void) insertAppMenu if (!self->_hasAppMenu) { NSMenuItem* apple = [[NSMenuItem alloc] init]; [apple setTitle:@"Apple"]; - + NSMenu* appleItems = [[NSMenu alloc] init]; [appleItems setTitle:@"Apple"]; [apple setSubmenu:appleItems]; @@ -661,7 +690,7 @@ -(void) showPopup eventNumber:0 clickCount:0 pressure:0]; - + [NSMenu popUpContextMenu:self.menu withEvent:fakeMouseEvent forView:[window contentView]]; } // Find the target associated with this ALLEGRO_MENU @@ -691,7 +720,7 @@ -(id) init self->_items = [[NSMutableArray alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onWindowChange:) name:NSWindowDidBecomeMainNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onWindowClose:) name:NSWindowWillCloseNotification object:nil]; - + } return self; } diff --git a/addons/native_dialog/win_dialog.c b/addons/native_dialog/win_dialog.c index 7a893ab71f..57c38c72e2 100644 --- a/addons/native_dialog/win_dialog.c +++ b/addons/native_dialog/win_dialog.c @@ -50,12 +50,12 @@ static bool got_wm_size_event = false; /* For Unicode support, define some helper functions * which work with either UTF-16 or ANSI code pages * depending on whether UNICODE is defined or not. - */ + */ /* tcreate_path: * Create path from TCHARs */ -static ALLEGRO_PATH* _tcreate_path(const TCHAR* ts) +static ALLEGRO_PATH* _tcreate_path(const TCHAR* ts) { char* tmp = _twin_tchar_to_utf8(ts); ALLEGRO_PATH* path = al_create_path(tmp); @@ -65,7 +65,7 @@ static ALLEGRO_PATH* _tcreate_path(const TCHAR* ts) /* tcreate_path: * Create directory path from TCHARs */ -static ALLEGRO_PATH* _tcreate_path_for_directory(const TCHAR* ts) +static ALLEGRO_PATH* _tcreate_path_for_directory(const TCHAR* ts) { char* tmp = _twin_tchar_to_utf8(ts); ALLEGRO_PATH* path = al_create_path_for_directory(tmp); @@ -100,7 +100,7 @@ static INT CALLBACK _browse_callback_proc(HWND hwnd, UINT uMsg, LPARAM unused, L return 0; } -static TCHAR* _extract_dirname(ALLEGRO_PATH *fullpath) +static TCHAR* _extract_dirname(ALLEGRO_PATH *fullpath) { TCHAR* wpath = NULL; bool is_dir; @@ -120,7 +120,7 @@ static TCHAR* _extract_dirname(ALLEGRO_PATH *fullpath) } else { /* Extract the directory from the path. */ - + ALLEGRO_PATH* initial_dir_path = NULL; initial_dir_path = al_clone_path(fullpath); if (initial_dir_path) { @@ -149,7 +149,7 @@ static bool select_folder(ALLEGRO_DISPLAY_WIN *win_display, folderinfo.lpszTitle = _twin_ustr_to_tchar(fd->title); folderinfo.ulFlags = 0; folderinfo.lpfn = NULL; - + if (fd->fc_initial_path) { wpath = _extract_dirname(fd->fc_initial_path); folderinfo.lpfn = _browse_callback_proc; @@ -171,49 +171,42 @@ static bool select_folder(ALLEGRO_DISPLAY_WIN *win_display, return false; } -static ALLEGRO_USTR *create_filter_string(const ALLEGRO_USTR *patterns) +static ALLEGRO_USTR *create_filter_string(const _AL_VECTOR *patterns) { ALLEGRO_USTR *filter = al_ustr_new(""); - bool filter_all = false; - int start, end; - - /* FIXME: Move all this filter parsing stuff into a common file. */ - if (0 == strcmp(al_cstr(patterns), "*.*")) { - filter_all = true; - } - else { - al_ustr_append_cstr(filter, "All Supported Files"); - al_ustr_append_chr(filter, '\0'); - start = al_ustr_size(filter); - al_ustr_append(filter, patterns); - - /* Remove all instances of "*.*", which will be added separately. */ - for (;;) { - int pos = al_ustr_find_cstr(filter, start, "*.*;"); - if (pos == -1) - break; - if (pos == start || al_ustr_get(filter, pos - 1) == ';') { - filter_all = true; - al_ustr_remove_range(filter, pos, pos + 4); - start = pos; - } - else { - start = pos + 4; - } + for (size_t i = 0; i < _al_vector_size(patterns); i++) + { + _AL_PATTERNS_AND_DESC *patterns_and_desc = _al_vector_ref(patterns, i); + bool any_valid = false; + for (size_t j = 0; j < _al_vector_size(&patterns_and_desc->patterns_vec); j++) { + _AL_PATTERN *pattern = _al_vector_ref(&patterns_and_desc->patterns_vec, j); + any_valid |= !pattern->is_mime; } - while (al_ustr_has_suffix_cstr(filter, ";*.*")) { - filter_all = true; - end = al_ustr_size(filter); - al_ustr_remove_range(filter, end - 4, end); + if (!any_valid) { + continue; + } + const ALLEGRO_USTR *desc = al_ref_info(&patterns_and_desc->desc); + if (al_ustr_size(desc) > 0) { + al_ustr_append(filter, desc); + } + else { + al_ustr_append_cstr(filter, "All supported files"); } - - al_ustr_append_chr(filter, '\0'); - } - - if (filter_all) { - al_ustr_append_cstr(filter, "All Files"); al_ustr_append_chr(filter, '\0'); - al_ustr_append_cstr(filter, "*.*"); + bool first = true; + for (size_t j = 0; j < _al_vector_size(&patterns_and_desc->patterns_vec); j++) { + _AL_PATTERN *pattern = _al_vector_ref(&patterns_and_desc->patterns_vec, j); + if (!pattern->is_mime) { + if (!first) { + al_ustr_append_chr(filter, ';'); + } + first = false; + if (pattern->is_catchall) + al_ustr_append_cstr(filter, "*.*"); + else + al_ustr_append(filter, al_ref_info(&pattern->info)); + } + } al_ustr_append_chr(filter, '\0'); } @@ -258,8 +251,8 @@ bool _al_show_native_file_dialog(ALLEGRO_DISPLAY *display, ofn.hwndOwner = win_display ? win_display->window : NULL; /* Create filter string. */ - if (fd->fc_patterns) { - filter_string = create_filter_string(fd->fc_patterns); + if (_al_vector_size(&fd->fc_patterns) > 0) { + filter_string = create_filter_string(&fd->fc_patterns); wfilter = _twin_ustr_to_tchar(filter_string); ofn.lpstrFilter = wfilter; } @@ -366,9 +359,9 @@ int _al_show_native_message_box(ALLEGRO_DISPLAY *display, type |= MB_ICONQUESTION; else if (fd->flags & ALLEGRO_MESSAGEBOX_WARN) type |= MB_ICONWARNING; - else if (fd->flags & ALLEGRO_MESSAGEBOX_ERROR) + else if (fd->flags & ALLEGRO_MESSAGEBOX_ERROR) type |= MB_ICONERROR; - else + else type |= MB_ICONINFORMATION; if (fd->flags & ALLEGRO_MESSAGEBOX_YES_NO) @@ -378,7 +371,7 @@ int _al_show_native_message_box(ALLEGRO_DISPLAY *display, /* heading + text are combined together */ - if (al_ustr_size(fd->mb_heading)) + if (al_ustr_size(fd->mb_heading)) al_ustr_append_cstr(fd->mb_heading, "\n\n"); al_ustr_append(fd->mb_heading, fd->mb_text); @@ -442,7 +435,7 @@ static void wlog_do_append_native_text_log(ALLEGRO_NATIVE_DIALOG *textlog) index = GetWindowTextLength(textlog->tl_textview); SendMessage(textlog->tl_textview, EM_SETSEL, (WPARAM)index, (LPARAM)index); convert_crlf(textlog->tl_pending_text); - TCHAR* buf = _twin_utf8_to_tchar(al_cstr(textlog->tl_pending_text)); + TCHAR* buf = _twin_utf8_to_tchar(al_cstr(textlog->tl_pending_text)); SendMessage(textlog->tl_textview, EM_REPLACESEL, 0, (LPARAM) buf); al_free(buf); al_ustr_truncate(textlog->tl_pending_text, 0); @@ -752,7 +745,7 @@ static bool menu_callback(ALLEGRO_DISPLAY *display, UINT msg, WPARAM wParam, LPA ALLEGRO_MENU *menu = (ALLEGRO_MENU *) lParam; HWND hwnd = al_get_win_window_handle(display); POINT pos; - GetCursorPos(&pos); + GetCursorPos(&pos); SetForegroundWindow(hwnd); TrackPopupMenuEx((HMENU) menu->extra1, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON, pos.x, pos.y, hwnd, NULL); @@ -783,7 +776,7 @@ static void init_menu_info(MENUITEMINFO *info, ALLEGRO_MENU_ITEM *menu) { memset(info, 0, sizeof(*info)); - info->cbSize = sizeof(*info); + info->cbSize = sizeof(*info); info->fMask = MIIM_FTYPE | MIIM_STATE | MIIM_ID | MIIM_SUBMENU | MIIM_STRING | MIIM_CHECKMARKS; info->wID = menu->unique_id; @@ -822,13 +815,13 @@ static void init_menu_info(MENUITEMINFO *info, ALLEGRO_MENU_ITEM *menu) bi.bmiHeader.biCompression = BI_RGB; hdc = GetDC(menu->parent->display ? al_get_win_window_handle(menu->parent->display) : NULL); - + hbmp = CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, (void **)&data, NULL, 0); lock = al_lock_bitmap(menu->icon, ALLEGRO_PIXEL_FORMAT_ARGB_8888, ALLEGRO_LOCK_READONLY); memcpy(data, lock->data, w * h * 4); al_unlock_bitmap(menu->icon); - + info->hbmpUnchecked = hbmp; menu->extra2 = hbmp; @@ -861,7 +854,7 @@ bool _al_insert_menu_item_at(ALLEGRO_MENU_ITEM *item, int i) { MENUITEMINFO info; init_menu_info(&info, item); - + InsertMenuItem((HMENU) item->parent->extra1, i, TRUE, &info); destroy_menu_info(&info); return true; @@ -896,7 +889,7 @@ bool _al_show_display_menu(ALLEGRO_DISPLAY *display, ALLEGRO_MENU *menu) if (!hwnd) return false; ASSERT(menu->extra1); - + /* Note that duplicate callbacks are automatically filtered out, so it's safe to call this many times. */ al_win_add_window_callback(display, menu_callback, NULL); @@ -914,7 +907,7 @@ bool _al_show_display_menu(ALLEGRO_DISPLAY *display, ALLEGRO_MENU *menu) } al_unlock_mutex(global_mutex); } - + return true; } @@ -939,7 +932,7 @@ bool _al_hide_display_menu(ALLEGRO_DISPLAY *display, ALLEGRO_MENU *menu) } (void) menu; - + return true; } diff --git a/android/gradle_project/allegro/src/main/java/org/liballeg/android/AllegroDialog.java b/android/gradle_project/allegro/src/main/java/org/liballeg/android/AllegroDialog.java index 084ac10fe4..31a3f12be1 100644 --- a/android/gradle_project/allegro/src/main/java/org/liballeg/android/AllegroDialog.java +++ b/android/gradle_project/allegro/src/main/java/org/liballeg/android/AllegroDialog.java @@ -504,10 +504,13 @@ private void reallyOpen(Activity activity, int flags, String[] mimeTypes, Uri in Intent intent = new Intent(action); if (0 != (flags & AllegroDialogConst.ALLEGRO_FILECHOOSER_SAVE)) { - // "Save file": must indicate a mime type - intent.setType("application/octet-stream"); // binary - //intent.setType("text/plain"); // plain text - if (mimeTypes.length > 0 && !mimeTypes[0].equals("*/*")) + // "Save file" + if (mimeTypes.length != 1) { + intent.setType("*/*"); + if (mimeTypes.length > 0) + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + } + else intent.setType(mimeTypes[0]); } else if (0 == (flags & AllegroDialogConst.ALLEGRO_FILECHOOSER_FOLDER)) { diff --git a/docs/src/refman/native_dialog.txt b/docs/src/refman/native_dialog.txt index 7214e48632..570c75ca60 100644 --- a/docs/src/refman/native_dialog.txt +++ b/docs/src/refman/native_dialog.txt @@ -58,11 +58,29 @@ Parameters: - `title`: Title of the dialog. -- `patterns`: A list of semi-colon separated patterns to match. This - should not contain any whitespace characters. If a pattern - contains the '/' character, then it is treated as a MIME type (e.g. - 'image/png'). Not all platforms support file patterns. If the native dialog - does not provide support, this parameter is ignored. +- `patterns`: A string containing newline separated sets of patterns to match. + A pattern is either a shell-style glob pattern (e.g. `"*.txt"`) or a MIME + type (e.g. `"image/png"`). Not all platforms support both (and some don't + even support globs), so a portable solution is to specify a MIME type and + simple style globs which Allegro will pick from to match what the platform + supports (e.g. do `"image/png;*.png"`). Multiple patterns are separated + using a semicolon. If the platform does not provide support for patterns, + this parameter is ignored. Here are some example patterns: + + - `"*.txt"` -- defines a single filter, matching `*.txt` files. + - `"*.txt;*.md"` -- like above, but matching two types of files. + - `"Text files (*.txt, *.md) *.txt;*.md"` -- like above, but with a custom + description (separated from the patterns using a space). + - `"Text files *.txt\nPNG images image/png;*.png"` -- defines two filters, + with the second filter using a MIME type and extension at the same time. + + > *Note:* Windows does not support MIME types. Android supports only MIME + > types. Instead of file patterns, MacOS supports extensions, so matching + > based on filename beyond file type does not work. Allegro will parse your + > file pattern to try to extract the file extension. MacOS also supports + > MIME types, which behave more predictably. MacOS does not support detailed + > descriptions or multiple pattern sets, Allegro will strip the detailed + > descriptions and concatenate all patterns into one list. - `mode`: 0, or a combination of the following flags: @@ -278,7 +296,7 @@ This ID should be unique per menu; if you duplicate IDs, then there will be no way for you to determine exactly which item generated the event. There are many functions that take `pos` as a parameter used for locating a -particular menu item. In those cases, it represents one of two things: an ID +particular menu item. In those cases, it represents one of two things: an ID or a zero-based index. Any value greater than zero will always be treated as an ID. Anything else (including zero) will be considered an index based on the absolute value. In other words, 0 is the first menu item, -1 is the second menu item, -2 @@ -393,7 +411,7 @@ See also: [al_create_menu], [al_build_menu] ### API: al_build_menu -Builds a menu based on the specifications of a sequence of +Builds a menu based on the specifications of a sequence of `ALLEGRO_MENU_INFO` elements. Returns a pointer to the root `ALLEGRO_MENU`, or `NULL` on failure. @@ -406,7 +424,7 @@ See also: [ALLEGRO_MENU_INFO], [al_create_menu], [al_create_popup_menu] ### API: al_append_menu_item -Appends a menu item to the end of the menu. See [al_insert_menu_item] +Appends a menu item to the end of the menu. See [al_insert_menu_item] for more information. Since: 5.1.0 @@ -419,7 +437,7 @@ Inserts a menu item at the spot specified. See the introductory text for a detailed explanation of how the `pos` parameter is interpreted. The `parent` menu can be a popup menu or a regular menu. To underline -one character in the `title`, prefix it with an ampersand. +one character in the `title`, prefix it with an ampersand. The `flags` can be any combination of: @@ -434,7 +452,7 @@ ALLEGRO_MENU_ITEM_CHECKBOX ALLEGRO_MENU_ITEM_CHECKED : The item is checked. If set, ALLEGRO_MENU_ITEM_CHECKBOX will automatically be set as well. - + The `icon` is not yet supported. The `submenu` parameter indicates that this item contains a child menu. @@ -525,7 +543,7 @@ See also: [al_set_menu_item_flags], [al_toggle_menu_item_flags] ### API: al_set_menu_item_flags Updates the menu item's flags. See [al_insert_menu_item] for a description -of the available flags. +of the available flags. Since: 5.1.0 @@ -534,7 +552,7 @@ See also: [al_get_menu_item_flags], [al_toggle_menu_item_flags] ### API: al_toggle_menu_item_flags Toggles the specified menu item's flags. See [al_insert_menu_item] for a -description of the available flags. +description of the available flags. Returns a bitfield of only the specified flags that are set after the toggle. A flag that was not toggled will not be returned, even if it is set. @@ -581,7 +599,7 @@ Returns the menu, if found. Otherwise returns `NULL`. Since: 5.1.0 See also: [al_find_menu_item] - + ### API: al_find_menu_item Searches in the `haystack` menu for an item with the given `id`. (Note that @@ -596,7 +614,7 @@ Returns true if the menu item was found. Since: 5.1.0 See also: [al_find_menu] - + ### API: al_get_default_menu_event_source Returns the default event source used for menu clicks. If a menu was not @@ -623,7 +641,7 @@ See also: [al_register_event_source], [al_get_default_menu_event_source], ### API: al_disable_menu_event_source -Disables a unique event source for the menu, causing it to use the +Disables a unique event source for the menu, causing it to use the default event source. Since: 5.1.0 @@ -653,7 +671,7 @@ to be resized! You should listen for a resize event, check how much space was lost, and resize the window accordingly if you want to maintain your window's prior size. -Returns true if successful. +Returns true if successful. Since: 5.1.0 @@ -666,7 +684,7 @@ created with [al_create_popup_menu]. It generates events just like a regular display menu does. It is possible that the menu will be canceled without any selection being made. -The `display` parameter indicates which window the menu is associated with +The `display` parameter indicates which window the menu is associated with (when you process the menu click event), but does not actually affect where the menu is located on the screen.