Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[macOS] Add support for native help menu search callbacks, integrate editor help. #83819

Merged
merged 1 commit into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,17 @@
Returns [code]true[/code] if the specified [param feature] is supported by the current [DisplayServer], [code]false[/code] otherwise.
</description>
</method>
<method name="help_set_search_callbacks">
<return type="void" />
<param index="0" name="search_callback" type="Callable" />
<param index="1" name="action_callback" type="Callable" />
<description>
Sets native help system search callbacks.
[param search_callback] has the following arguments: [code]String search_string, int result_limit[/code] and return a [Dictionary] with "key, display name" pairs for the search results. Called when the user enters search terms in the [code]Help[/code] menu.
[param action_callback] has the following arguments: [code]String key[/code]. Called when the user selects a search result in the [code]Help[/code] menu.
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
<method name="ime_get_selection" qualifiers="const">
<return type="Vector2i" />
<description>
Expand Down Expand Up @@ -1791,6 +1802,9 @@
<constant name="FEATURE_STATUS_INDICATOR" value="22" enum="Feature">
Display server supports application status indicators.
</constant>
<constant name="FEATURE_NATIVE_HELP" value="23" enum="Feature">
Display server supports native help system search callbacks. See [method help_set_search_callbacks].
</constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden.
</constant>
Expand Down
107 changes: 107 additions & 0 deletions editor/editor_help_search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,101 @@
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"

bool EditorHelpSearch::_all_terms_in_name(const Vector<String> &p_terms, const String &p_name) const {
for (int i = 0; i < p_terms.size(); i++) {
if (p_name.findn(p_terms[i]) < 0) {
return false;
}
}
return true;
}

void EditorHelpSearch::_match_method_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::MethodDoc> &p_methods, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const {
// Constructors, Methods, Operators...
for (int i = 0; i < p_methods.size(); i++) {
String method_name = p_methods[i].name.to_lower();
if (_all_terms_in_name(p_terms, method_name) ||
(p_term.begins_with(".") && method_name.begins_with(p_term.substr(1))) ||
(p_term.ends_with("(") && method_name.ends_with(p_term.left(p_term.length() - 1).strip_edges())) ||
(p_term.begins_with(".") && p_term.ends_with("(") && method_name == p_term.substr(1, p_term.length() - 2).strip_edges())) {
r_result[vformat("class_%s:%s:%s", p_metatype, p_class_name, p_methods[i].name)] = vformat("%s > %s: %s", p_class_name, p_type, p_methods[i].name);
}
}
}

void EditorHelpSearch::_match_const_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::ConstantDoc> &p_constants, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const {
for (int i = 0; i < p_constants.size(); i++) {
String method_name = p_constants[i].name.to_lower();
if (_all_terms_in_name(p_terms, method_name) ||
(p_term.begins_with(".") && method_name.begins_with(p_term.substr(1))) ||
(p_term.ends_with("(") && method_name.ends_with(p_term.left(p_term.length() - 1).strip_edges())) ||
(p_term.begins_with(".") && p_term.ends_with("(") && method_name == p_term.substr(1, p_term.length() - 2).strip_edges())) {
r_result[vformat("class_%s:%s:%s", p_metatype, p_class_name, p_constants[i].name)] = vformat("%s > %s: %s", p_class_name, p_type, p_constants[i].name);
}
}
}

void EditorHelpSearch::_match_property_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::PropertyDoc> &p_properties, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const {
for (int i = 0; i < p_properties.size(); i++) {
String method_name = p_properties[i].name.to_lower();
if (_all_terms_in_name(p_terms, method_name) ||
(p_term.begins_with(".") && method_name.begins_with(p_term.substr(1))) ||
(p_term.ends_with("(") && method_name.ends_with(p_term.left(p_term.length() - 1).strip_edges())) ||
(p_term.begins_with(".") && p_term.ends_with("(") && method_name == p_term.substr(1, p_term.length() - 2).strip_edges())) {
r_result[vformat("class_%s:%s:%s", p_metatype, p_class_name, p_properties[i].name)] = vformat("%s > %s: %s", p_class_name, p_type, p_properties[i].name);
}
}
}

void EditorHelpSearch::_match_theme_property_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::ThemeItemDoc> &p_properties, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const {
for (int i = 0; i < p_properties.size(); i++) {
String method_name = p_properties[i].name.to_lower();
if (_all_terms_in_name(p_terms, method_name) ||
(p_term.begins_with(".") && method_name.begins_with(p_term.substr(1))) ||
(p_term.ends_with("(") && method_name.ends_with(p_term.left(p_term.length() - 1).strip_edges())) ||
(p_term.begins_with(".") && p_term.ends_with("(") && method_name == p_term.substr(1, p_term.length() - 2).strip_edges())) {
r_result[vformat("class_%s:%s:%s", p_metatype, p_class_name, p_properties[i].name)] = vformat("%s > %s: %s", p_class_name, p_type, p_properties[i].name);
}
}
}

Dictionary EditorHelpSearch::_native_search_cb(const String &p_search_string, int p_result_limit) {
Dictionary ret;
const String &term = p_search_string.strip_edges().to_lower();
Vector<String> terms = term.split_spaces();
if (terms.is_empty()) {
terms.append(term);
}

for (HashMap<String, DocData::ClassDoc>::Iterator iterator_doc = EditorHelp::get_doc_data()->class_list.begin(); iterator_doc; ++iterator_doc) {
DocData::ClassDoc &class_doc = iterator_doc->value;
if (class_doc.name.is_empty()) {
continue;
}
if (class_doc.name.findn(term) > -1) {
ret[vformat("class_name:%s", class_doc.name)] = class_doc.name;
}
if (term.length() > 1 || term == "@") {
_match_method_name_and_push_back(term, terms, class_doc.constructors, TTRC("Constructor"), "method", class_doc.name, ret);
_match_method_name_and_push_back(term, terms, class_doc.methods, TTRC("Method"), "method", class_doc.name, ret);
_match_method_name_and_push_back(term, terms, class_doc.operators, TTRC("Operator"), "method", class_doc.name, ret);
_match_method_name_and_push_back(term, terms, class_doc.signals, TTRC("Signal"), "signal", class_doc.name, ret);
_match_const_name_and_push_back(term, terms, class_doc.constants, TTRC("Constant"), "constant", class_doc.name, ret);
_match_property_name_and_push_back(term, terms, class_doc.properties, TTRC("Property"), "property", class_doc.name, ret);
_match_theme_property_name_and_push_back(term, terms, class_doc.theme_properties, TTRC("Theme Property"), "theme_item", class_doc.name, ret);
_match_method_name_and_push_back(term, terms, class_doc.annotations, TTRC("Annotation"), "annotation", class_doc.name, ret);
}
if (ret.size() > p_result_limit) {
break;
}
}
return ret;
}

void EditorHelpSearch::_native_action_cb(const String &p_item_string) {
emit_signal(SNAME("go_to_help"), p_item_string);
}

void EditorHelpSearch::_update_results() {
String term = search_box->get_text();

Expand Down Expand Up @@ -94,6 +189,18 @@ void EditorHelpSearch::_confirmed() {

void EditorHelpSearch::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_HELP)) {
DisplayServer::get_singleton()->help_set_search_callbacks(callable_mp(this, &EditorHelpSearch::_native_search_cb), callable_mp(this, &EditorHelpSearch::_native_action_cb));
}
} break;

case NOTIFICATION_EXIT_TREE: {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_NATIVE_HELP)) {
DisplayServer::get_singleton()->help_set_search_callbacks();
}
} break;

case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
tree_cache.clear();
Expand Down
9 changes: 9 additions & 0 deletions editor/editor_help_search.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ class EditorHelpSearch : public ConfirmationDialog {
void _filter_combo_item_selected(int p_option);
void _confirmed();

bool _all_terms_in_name(const Vector<String> &p_terms, const String &p_name) const;
void _match_method_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::MethodDoc> &p_methods, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const;
void _match_const_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::ConstantDoc> &p_constants, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const;
void _match_property_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::PropertyDoc> &p_properties, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const;
void _match_theme_property_name_and_push_back(const String &p_term, const Vector<String> &p_terms, Vector<DocData::ThemeItemDoc> &p_properties, const String &p_type, const String &p_metatype, const String &p_class_name, Dictionary &r_result) const;

Dictionary _native_search_cb(const String &p_search_string, int p_result_limit);
void _native_action_cb(const String &p_item_string);

protected:
void _notification(int p_what);
static void _bind_methods();
Expand Down
7 changes: 7 additions & 0 deletions platform/macos/display_server_macos.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@ class DisplayServerMacOS : public DisplayServer {

IOPMAssertionID screen_keep_on_assertion = kIOPMNullAssertionID;

Callable help_search_callback;
Callable help_action_callback;

struct MenuCall {
Variant tag;
Callable callback;
Expand Down Expand Up @@ -282,6 +285,10 @@ class DisplayServerMacOS : public DisplayServer {
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;

virtual void help_set_search_callbacks(const Callable &p_search_callback = Callable(), const Callable &p_action_callback = Callable()) override;
Callable _help_get_search_callback() const;
Callable _help_get_action_callback() const;

virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()) override;

virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
Expand Down
14 changes: 14 additions & 0 deletions platform/macos/display_server_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,7 @@
case FEATURE_EXTEND_TO_TITLE:
case FEATURE_SCREEN_CAPTURE:
case FEATURE_STATUS_INDICATOR:
case FEATURE_NATIVE_HELP:
return true;
default: {
}
Expand All @@ -851,6 +852,19 @@
return "macOS";
}

void DisplayServerMacOS::help_set_search_callbacks(const Callable &p_search_callback, const Callable &p_action_callback) {
help_search_callback = p_search_callback;
help_action_callback = p_action_callback;
}

Callable DisplayServerMacOS::_help_get_search_callback() const {
return help_search_callback;
}

Callable DisplayServerMacOS::_help_get_action_callback() const {
return help_action_callback;
}

bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const {
if (submenu_inv.has(p_menu)) {
const MenuData &md = submenu[submenu_inv[p_menu]];
Expand Down
2 changes: 1 addition & 1 deletion platform/macos/godot_application_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>

@interface GodotApplicationDelegate : NSObject
@interface GodotApplicationDelegate : NSObject <NSUserInterfaceItemSearching, NSApplicationDelegate>
- (void)forceUnbundledWindowActivationHackStep1;
- (void)forceUnbundledWindowActivationHackStep2;
- (void)forceUnbundledWindowActivationHackStep3;
Expand Down
53 changes: 53 additions & 0 deletions platform/macos/godot_application_delegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,59 @@ - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
return YES;
}

- (NSArray<NSString *> *)localizedTitlesForItem:(id)item {
NSArray *item_name = @[ item[1] ];
return item_name;
}

- (void)searchForItemsWithSearchString:(NSString *)searchString resultLimit:(NSInteger)resultLimit matchedItemHandler:(void (^)(NSArray *items))handleMatchedItems {
NSMutableArray *found_items = [[NSMutableArray alloc] init];

DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (ds && ds->_help_get_search_callback().is_valid()) {
Callable cb = ds->_help_get_search_callback();

Variant ret;
Variant search_string = String::utf8([searchString UTF8String]);
Variant result_limit = (uint64_t)resultLimit;
Callable::CallError ce;
const Variant *args[2] = { &search_string, &result_limit };

cb.callp(args, 2, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_PRINT(vformat(RTR("Failed to execute help search callback: %s."), Variant::get_callable_error_text(cb, args, 2, ce)));
}
Dictionary results = ret;
for (const Variant *E = results.next(); E; E = results.next(E)) {
const String &key = *E;
const String &value = results[*E];
if (key.length() > 0 && value.length() > 0) {
NSArray *item = @[ [NSString stringWithUTF8String:key.utf8().get_data()], [NSString stringWithUTF8String:value.utf8().get_data()] ];
[found_items addObject:item];
}
}
}

handleMatchedItems(found_items);
}

- (void)performActionForItem:(id)item {
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (ds && ds->_help_get_action_callback().is_valid()) {
Callable cb = ds->_help_get_action_callback();

Variant ret;
Variant item_string = String::utf8([item[0] UTF8String]);
Callable::CallError ce;
const Variant *args[1] = { &item_string };

cb.callp(args, 1, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_PRINT(vformat(RTR("Failed to execute help action callback: %s."), Variant::get_callable_error_text(cb, args, 1, ce)));
}
}
}

- (void)forceUnbundledWindowActivationHackStep1 {
// Step 1: Switch focus to macOS SystemUIServer process.
// Required to perform step 2, TransformProcessType will fail if app is already the in focus.
Expand Down
1 change: 1 addition & 0 deletions platform/macos/os_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,7 @@
id delegate = [[GodotApplicationDelegate alloc] init];
ERR_FAIL_NULL(delegate);
[NSApp setDelegate:delegate];
[NSApp registerUserInterfaceItemSearchHandler:delegate];

pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
Expand Down
7 changes: 7 additions & 0 deletions servers/display_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ DisplayServer::DisplayServerCreate DisplayServer::server_create_functions[Displa

int DisplayServer::server_create_count = 1;

void DisplayServer::help_set_search_callbacks(const Callable &p_search_callback, const Callable &p_action_callback) {
WARN_PRINT("Native help is not supported by this display server.");
}

int DisplayServer::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
Expand Down Expand Up @@ -633,6 +637,8 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_feature", "feature"), &DisplayServer::has_feature);
ClassDB::bind_method(D_METHOD("get_name"), &DisplayServer::get_name);

ClassDB::bind_method(D_METHOD("help_set_search_callbacks", "search_callback", "action_callback"), &DisplayServer::help_set_search_callbacks);

ClassDB::bind_method(D_METHOD("global_menu_set_popup_callbacks", "menu_root", "open_callback", "close_callback"), &DisplayServer::global_menu_set_popup_callbacks);
ClassDB::bind_method(D_METHOD("global_menu_add_submenu_item", "menu_root", "label", "submenu", "index"), &DisplayServer::global_menu_add_submenu_item, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
Expand Down Expand Up @@ -879,6 +885,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_EXTEND_TO_TITLE);
BIND_ENUM_CONSTANT(FEATURE_SCREEN_CAPTURE);
BIND_ENUM_CONSTANT(FEATURE_STATUS_INDICATOR);
BIND_ENUM_CONSTANT(FEATURE_NATIVE_HELP);

BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
Expand Down
3 changes: 3 additions & 0 deletions servers/display_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,14 @@ class DisplayServer : public Object {
FEATURE_EXTEND_TO_TITLE,
FEATURE_SCREEN_CAPTURE,
FEATURE_STATUS_INDICATOR,
FEATURE_NATIVE_HELP,
};

virtual bool has_feature(Feature p_feature) const = 0;
virtual String get_name() const = 0;

virtual void help_set_search_callbacks(const Callable &p_search_callback = Callable(), const Callable &p_action_callback = Callable());

virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable());

virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1);
Expand Down
Loading