diff options
Diffstat (limited to 'platform/macos')
| -rw-r--r-- | platform/macos/SCsub | 1 | ||||
| -rw-r--r-- | platform/macos/display_server_macos.h | 3 | ||||
| -rw-r--r-- | platform/macos/display_server_macos.mm | 268 | ||||
| -rw-r--r-- | platform/macos/godot_content_view.mm | 26 | ||||
| -rw-r--r-- | platform/macos/godot_open_save_delegate.h | 66 | ||||
| -rw-r--r-- | platform/macos/godot_open_save_delegate.mm | 250 |
6 files changed, 448 insertions, 166 deletions
diff --git a/platform/macos/SCsub b/platform/macos/SCsub index 30202e5de7..ad19f12c58 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -20,6 +20,7 @@ files = [ "godot_main_macos.mm", "godot_menu_delegate.mm", "godot_menu_item.mm", + "godot_open_save_delegate.mm", "dir_access_macos.mm", "tts_macos.mm", "joypad_macos.cpp", diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index cc343a6d64..cdd17a7e76 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -234,6 +234,8 @@ private: int _get_system_menu_count(const NSMenu *p_menu) const; NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out); + Error _file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb); + public: NSMenu *get_dock_menu() const; void menu_callback(id p_sender); @@ -345,6 +347,7 @@ public: virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override; + virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override; virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 378688f78a..2bfe16828a 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -34,6 +34,7 @@ #include "godot_content_view.h" #include "godot_menu_delegate.h" #include "godot_menu_item.h" +#include "godot_open_save_delegate.h" #include "godot_window.h" #include "godot_window_delegate.h" #include "key_mapping_macos.h" @@ -2079,139 +2080,37 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect return OK; } -@interface FileDialogDropdown : NSObject { - NSSavePanel *dialog; - NSMutableArray *allowed_types; - int cur_index; -} - -- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types; -- (void)popupAction:(id)sender; -- (int)getIndex; - -@end - -@implementation FileDialogDropdown - -- (int)getIndex { - return cur_index; -} - -- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types { - if ((self = [super init])) { - dialog = p_dialog; - allowed_types = p_allowed_types; - cur_index = 0; - } - return self; +Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { + return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false); } -- (void)popupAction:(id)sender { - NSUInteger index = [sender indexOfSelectedItem]; - if (index < [allowed_types count]) { - [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]]; - cur_index = index; - } else { - [dialog setAllowedFileTypes:@[]]; - cur_index = -1; - } +Error DisplayServerMacOS::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) { + return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true); } -@end - -FileDialogDropdown *_make_accessory_view(NSSavePanel *p_panel, const Vector<String> &p_filters) { - NSView *group = [[NSView alloc] initWithFrame:NSZeroRect]; - group.translatesAutoresizingMaskIntoConstraints = NO; - - NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]]; - label.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(macOS 10.14, *)) { - label.textColor = NSColor.secondaryLabelColor; - } - if (@available(macOS 11.10, *)) { - label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; - } - [group addSubview:label]; - - NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; - popup.translatesAutoresizingMaskIntoConstraints = NO; - - NSMutableArray *allowed_types = [[NSMutableArray alloc] init]; - bool allow_other = false; - for (int i = 0; i < p_filters.size(); i++) { - Vector<String> tokens = p_filters[i].split(";"); - if (tokens.size() >= 1) { - String flt = tokens[0].strip_edges(); - int filter_slice_count = flt.get_slice_count(","); - - NSMutableArray *type_filters = [[NSMutableArray alloc] init]; - for (int j = 0; j < filter_slice_count; j++) { - String str = (flt.get_slice(",", j).strip_edges()); - if (str.strip_edges() == "*.*" || str.strip_edges() == "*") { - allow_other = true; - } else if (!str.is_empty()) { - [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]]; - } - } - - if ([type_filters count] > 0) { - NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()]; - [allowed_types addObject:type_filters]; - [popup addItemWithTitle:name_str]; - } - } - } - FileDialogDropdown *handler = [[FileDialogDropdown alloc] initWithDialog:p_panel fileTypes:allowed_types]; - popup.target = handler; - popup.action = @selector(popupAction:); - - [group addSubview:popup]; - - NSView *view = [[NSView alloc] initWithFrame:NSZeroRect]; - view.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:group]; - - NSMutableArray *constraints = [NSMutableArray array]; - [constraints addObject:[popup.topAnchor constraintEqualToAnchor:group.topAnchor constant:10]]; - [constraints addObject:[label.leadingAnchor constraintEqualToAnchor:group.leadingAnchor constant:10]]; - [constraints addObject:[popup.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:10]]; - [constraints addObject:[popup.firstBaselineAnchor constraintEqualToAnchor:label.firstBaselineAnchor]]; - [constraints addObject:[group.trailingAnchor constraintEqualToAnchor:popup.trailingAnchor constant:10]]; - [constraints addObject:[group.bottomAnchor constraintEqualToAnchor:popup.bottomAnchor constant:10]]; - [constraints addObject:[group.topAnchor constraintEqualToAnchor:view.topAnchor]]; - [constraints addObject:[group.centerXAnchor constraintEqualToAnchor:view.centerXAnchor]]; - [constraints addObject:[view.bottomAnchor constraintEqualToAnchor:group.bottomAnchor]]; - [NSLayoutConstraint activateConstraints:constraints]; - - [p_panel setAllowsOtherFileTypes:allow_other]; - if ([allowed_types count] > 0) { - [p_panel setAccessoryView:view]; - [p_panel setAllowedFileTypes:[allowed_types objectAtIndex:0]]; - } - - return handler; -} - -Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) { +Error DisplayServerMacOS::_file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) { _THREAD_SAFE_METHOD_ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED); NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()]; - FileDialogDropdown *handler = nullptr; - WindowID prev_focus = last_focused_window; + GodotOpenSaveDelegate *panel_delegate = [[GodotOpenSaveDelegate alloc] init]; + if (p_root.length() > 0) { + [panel_delegate setRootPath:p_root]; + } Callable callback = p_callback; // Make a copy for async completion handler. if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) { NSSavePanel *panel = [NSSavePanel savePanel]; [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; - handler = _make_accessory_view(panel, p_filters); + [panel_delegate makeAccessoryView:panel filters:p_filters options:p_options]; [panel setExtensionHidden:YES]; [panel setCanSelectHiddenExtension:YES]; [panel setCanCreateDirectories:YES]; [panel setShowsHiddenFiles:p_show_hidden]; + [panel setDelegate:panel_delegate]; if (p_filename != "") { NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; [panel setNameFieldStringValue:fileurl]; @@ -2248,30 +2147,60 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & url.parse_utf8([[[panel URL] path] UTF8String]); files.push_back(url); if (!callback.is_null()) { - Variant v_result = true; - Variant v_files = files; - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } else { if (!callback.is_null()) { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } @@ -2283,13 +2212,14 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; - handler = _make_accessory_view(panel, p_filters); + [panel_delegate makeAccessoryView:panel filters:p_filters options:p_options]; [panel setExtensionHidden:YES]; [panel setCanSelectHiddenExtension:YES]; [panel setCanCreateDirectories:YES]; [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)]; [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)]; [panel setShowsHiddenFiles:p_show_hidden]; + [panel setDelegate:panel_delegate]; if (p_filename != "") { NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; [panel setNameFieldStringValue:fileurl]; @@ -2333,30 +2263,60 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & files.push_back(url); } if (!callback.is_null()) { - Variant v_result = true; - Variant v_files = files; - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } else { if (!callback.is_null()) { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm index 139411249c..61065291d7 100644 --- a/platform/macos/godot_content_view.mm +++ b/platform/macos/godot_content_view.mm @@ -576,21 +576,23 @@ String u32text; u32text.parse_utf16(text.ptr(), text.length()); + DisplayServerMacOS::KeyEvent ke; + ke.window_id = window_id; + ke.macos_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], false); + ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); + ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); + ke.raw = true; + + if (u32text.is_empty()) { + ke.unicode = 0; + ds->push_to_key_event_buffer(ke); + } for (int i = 0; i < u32text.length(); i++) { const char32_t codepoint = u32text[i]; - - DisplayServerMacOS::KeyEvent ke; - - ke.window_id = window_id; - ke.macos_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], false); - ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); - ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); ke.unicode = fix_unicode(codepoint); - ke.raw = true; - ds->push_to_key_event_buffer(ke); } } else { diff --git a/platform/macos/godot_open_save_delegate.h b/platform/macos/godot_open_save_delegate.h new file mode 100644 index 0000000000..8857ef1fa9 --- /dev/null +++ b/platform/macos/godot_open_save_delegate.h @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* godot_open_save_delegate.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GODOT_OPEN_SAVE_DELEGATE_H +#define GODOT_OPEN_SAVE_DELEGATE_H + +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +#include "core/templates/hash_map.h" +#include "core/variant/typed_array.h" +#include "core/variant/variant.h" + +@interface GodotOpenSaveDelegate : NSObject <NSOpenSavePanelDelegate> { + NSSavePanel *dialog; + NSMutableArray *allowed_types; + + HashMap<int, String> ctr_ids; + Dictionary options; + int cur_index; + int ctr_id; + + String root; +} + +- (void)makeAccessoryView:(NSSavePanel *)p_panel filters:(const Vector<String> &)p_filters options:(const TypedArray<Dictionary> &)p_options; +- (void)setFileTypes:(NSMutableArray *)p_allowed_types; +- (void)popupOptionAction:(id)p_sender; +- (void)popupCheckAction:(id)p_sender; +- (void)popupFileAction:(id)p_sender; +- (int)getIndex; +- (Dictionary)getSelection; +- (int)setDefaultInt:(const String &)p_name value:(int)p_value; +- (int)setDefaultBool:(const String &)p_name value:(bool)p_value; +- (void)setRootPath:(const String &)p_root_path; + +@end + +#endif // GODOT_OPEN_SAVE_DELEGATE_H diff --git a/platform/macos/godot_open_save_delegate.mm b/platform/macos/godot_open_save_delegate.mm new file mode 100644 index 0000000000..306015d644 --- /dev/null +++ b/platform/macos/godot_open_save_delegate.mm @@ -0,0 +1,250 @@ +/**************************************************************************/ +/* godot_open_save_delegate.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "godot_open_save_delegate.h" + +@implementation GodotOpenSaveDelegate + +- (instancetype)init { + self = [super init]; + if ((self = [super init])) { + dialog = nullptr; + cur_index = 0; + ctr_id = 1; + allowed_types = nullptr; + root = String(); + } + return self; +} + +- (void)makeAccessoryView:(NSSavePanel *)p_panel filters:(const Vector<String> &)p_filters options:(const TypedArray<Dictionary> &)p_options { + dialog = p_panel; + + NSMutableArray *constraints = [NSMutableArray array]; + + NSView *base_view = [[NSView alloc] initWithFrame:NSZeroRect]; + base_view.translatesAutoresizingMaskIntoConstraints = NO; + + NSGridView *view = [NSGridView gridViewWithNumberOfColumns:2 rows:0]; + view.translatesAutoresizingMaskIntoConstraints = NO; + view.columnSpacing = 10; + view.rowSpacing = 10; + view.rowAlignment = NSGridRowAlignmentLastBaseline; + + int option_count = 0; + + for (int i = 0; i < p_options.size(); i++) { + const Dictionary &item = p_options[i]; + if (!item.has("name") || !item.has("values") || !item.has("default")) { + continue; + } + const String &name = item["name"]; + const Vector<String> &values = item["values"]; + int default_idx = item["default"]; + + NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:name.utf8().get_data()]]; + if (@available(macOS 10.14, *)) { + label.textColor = NSColor.secondaryLabelColor; + } + if (@available(macOS 11.10, *)) { + label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + } + + NSView *popup = nullptr; + if (values.is_empty()) { + NSButton *popup_check = [NSButton checkboxWithTitle:@"" target:self action:@selector(popupCheckAction:)]; + int tag = [self setDefaultBool:name value:(bool)default_idx]; + popup_check.state = (default_idx) ? NSControlStateValueOn : NSControlStateValueOff; + popup_check.tag = tag; + popup = popup_check; + } else { + NSPopUpButton *popup_list = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; + for (int i = 0; i < values.size(); i++) { + [popup_list addItemWithTitle:[NSString stringWithUTF8String:values[i].utf8().get_data()]]; + } + int tag = [self setDefaultInt:name value:default_idx]; + [popup_list selectItemAtIndex:default_idx]; + popup_list.tag = tag; + popup_list.target = self; + popup_list.action = @selector(popupOptionAction:); + popup = popup_list; + } + + [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]]; + + option_count++; + } + + NSMutableArray *new_allowed_types = [[NSMutableArray alloc] init]; + bool allow_other = false; + { + NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]]; + if (@available(macOS 10.14, *)) { + label.textColor = NSColor.secondaryLabelColor; + } + if (@available(macOS 11.10, *)) { + label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + } + + NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; + if (p_filters.is_empty()) { + [popup addItemWithTitle:@"All Files"]; + } + + for (int i = 0; i < p_filters.size(); i++) { + Vector<String> tokens = p_filters[i].split(";"); + if (tokens.size() >= 1) { + String flt = tokens[0].strip_edges(); + int filter_slice_count = flt.get_slice_count(","); + + NSMutableArray *type_filters = [[NSMutableArray alloc] init]; + for (int j = 0; j < filter_slice_count; j++) { + String str = (flt.get_slice(",", j).strip_edges()); + if (str.strip_edges() == "*.*" || str.strip_edges() == "*") { + allow_other = true; + } else if (!str.is_empty()) { + [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]]; + } + } + + if ([type_filters count] > 0) { + NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()]; + [new_allowed_types addObject:type_filters]; + [popup addItemWithTitle:name_str]; + } + } + } + [self setFileTypes:new_allowed_types]; + popup.target = self; + popup.action = @selector(popupFileAction:); + + [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]]; + } + + [base_view addSubview:view]; + [constraints addObject:[view.topAnchor constraintEqualToAnchor:base_view.topAnchor constant:10]]; + [constraints addObject:[base_view.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:10]]; + [constraints addObject:[base_view.centerXAnchor constraintEqualToAnchor:view.centerXAnchor constant:10]]; + [NSLayoutConstraint activateConstraints:constraints]; + + [p_panel setAllowsOtherFileTypes:allow_other]; + if (option_count > 0 || [new_allowed_types count] > 0) { + [p_panel setAccessoryView:base_view]; + } + if ([new_allowed_types count] > 0) { + [p_panel setAllowedFileTypes:[new_allowed_types objectAtIndex:0]]; + } +} + +- (int)getIndex { + return cur_index; +} + +- (Dictionary)getSelection { + return options; +} + +- (int)setDefaultInt:(const String &)p_name value:(int)p_value { + int cid = ctr_id++; + options[p_name] = p_value; + ctr_ids[cid] = p_name; + + return cid; +} + +- (int)setDefaultBool:(const String &)p_name value:(bool)p_value { + int cid = ctr_id++; + options[p_name] = p_value; + ctr_ids[cid] = p_name; + + return cid; +} + +- (void)setFileTypes:(NSMutableArray *)p_allowed_types { + allowed_types = p_allowed_types; +} + +- (instancetype)initWithDialog:(NSSavePanel *)p_dialog { + if ((self = [super init])) { + dialog = p_dialog; + cur_index = 0; + ctr_id = 1; + allowed_types = nullptr; + } + return self; +} + +- (void)popupCheckAction:(id)p_sender { + NSButton *btn = p_sender; + if (btn && ctr_ids.has(btn.tag)) { + options[ctr_ids[btn.tag]] = ([btn state] == NSControlStateValueOn); + } +} + +- (void)popupOptionAction:(id)p_sender { + NSPopUpButton *btn = p_sender; + if (btn && ctr_ids.has(btn.tag)) { + options[ctr_ids[btn.tag]] = (int)[btn indexOfSelectedItem]; + } +} + +- (void)popupFileAction:(id)p_sender { + NSPopUpButton *btn = p_sender; + if (btn) { + NSUInteger index = [btn indexOfSelectedItem]; + if (allowed_types && index < [allowed_types count]) { + [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]]; + cur_index = index; + } else { + [dialog setAllowedFileTypes:@[]]; + cur_index = -1; + } + } +} + +- (void)setRootPath:(const String &)p_root_path { + root = p_root_path; +} + +- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError *_Nullable *)outError { + if (root.is_empty()) { + return YES; + } + + NSString *ns_path = url.URLByStandardizingPath.URLByResolvingSymlinksInPath.path; + String path = String::utf8([ns_path UTF8String]).simplify_path(); + if (!path.begins_with(root.simplify_path())) { + return NO; + } + + return YES; +} + +@end |
