Browse Source

[macOS] Add methods to modify global and dock menus. Add ability to open multiple editor/project manager instances, recent/favourite project list to project manager dock menu and opened scene list to editor dock menu.

bruvzg 6 years ago
parent
commit
db6d4352ea

+ 30 - 0
core/bind/core_bind.cpp

@@ -185,10 +185,31 @@ _ResourceSaver::_ResourceSaver() {
 
 
 /////////////////OS
 /////////////////OS
 
 
+void _OS::global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta) {
+
+	OS::get_singleton()->global_menu_add_item(p_menu, p_label, p_signal, p_meta);
+}
+
+void _OS::global_menu_add_separator(const String &p_menu) {
+
+	OS::get_singleton()->global_menu_add_separator(p_menu);
+}
+
+void _OS::global_menu_remove_item(const String &p_menu, int p_idx) {
+
+	OS::get_singleton()->global_menu_remove_item(p_menu, p_idx);
+}
+
+void _OS::global_menu_clear(const String &p_menu) {
+
+	OS::get_singleton()->global_menu_clear(p_menu);
+}
+
 Point2 _OS::get_mouse_position() const {
 Point2 _OS::get_mouse_position() const {
 
 
 	return OS::get_singleton()->get_mouse_position();
 	return OS::get_singleton()->get_mouse_position();
 }
 }
+
 void _OS::set_window_title(const String &p_title) {
 void _OS::set_window_title(const String &p_title) {
 
 
 	OS::get_singleton()->set_window_title(p_title);
 	OS::get_singleton()->set_window_title(p_title);
@@ -202,6 +223,7 @@ int _OS::get_mouse_button_state() const {
 String _OS::get_unique_id() const {
 String _OS::get_unique_id() const {
 	return OS::get_singleton()->get_unique_id();
 	return OS::get_singleton()->get_unique_id();
 }
 }
+
 bool _OS::has_touchscreen_ui_hint() const {
 bool _OS::has_touchscreen_ui_hint() const {
 
 
 	return OS::get_singleton()->has_touchscreen_ui_hint();
 	return OS::get_singleton()->has_touchscreen_ui_hint();
@@ -211,6 +233,7 @@ void _OS::set_clipboard(const String &p_text) {
 
 
 	OS::get_singleton()->set_clipboard(p_text);
 	OS::get_singleton()->set_clipboard(p_text);
 }
 }
+
 String _OS::get_clipboard() const {
 String _OS::get_clipboard() const {
 
 
 	return OS::get_singleton()->get_clipboard();
 	return OS::get_singleton()->get_clipboard();
@@ -257,12 +280,14 @@ void _OS::set_video_mode(const Size2 &p_size, bool p_fullscreen, bool p_resizeab
 	vm.resizable = p_resizeable;
 	vm.resizable = p_resizeable;
 	OS::get_singleton()->set_video_mode(vm, p_screen);
 	OS::get_singleton()->set_video_mode(vm, p_screen);
 }
 }
+
 Size2 _OS::get_video_mode(int p_screen) const {
 Size2 _OS::get_video_mode(int p_screen) const {
 
 
 	OS::VideoMode vm;
 	OS::VideoMode vm;
 	vm = OS::get_singleton()->get_video_mode(p_screen);
 	vm = OS::get_singleton()->get_video_mode(p_screen);
 	return Size2(vm.width, vm.height);
 	return Size2(vm.width, vm.height);
 }
 }
+
 bool _OS::is_video_mode_fullscreen(int p_screen) const {
 bool _OS::is_video_mode_fullscreen(int p_screen) const {
 
 
 	OS::VideoMode vm;
 	OS::VideoMode vm;
@@ -1125,6 +1150,11 @@ void _OS::_bind_methods() {
 	//ClassDB::bind_method(D_METHOD("is_video_mode_resizable","screen"),&_OS::is_video_mode_resizable,DEFVAL(0));
 	//ClassDB::bind_method(D_METHOD("is_video_mode_resizable","screen"),&_OS::is_video_mode_resizable,DEFVAL(0));
 	//ClassDB::bind_method(D_METHOD("get_fullscreen_mode_list","screen"),&_OS::get_fullscreen_mode_list,DEFVAL(0));
 	//ClassDB::bind_method(D_METHOD("get_fullscreen_mode_list","screen"),&_OS::get_fullscreen_mode_list,DEFVAL(0));
 
 
+	ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu", "label", "id", "meta"), &_OS::global_menu_add_item);
+	ClassDB::bind_method(D_METHOD("global_menu_add_separator", "menu"), &_OS::global_menu_add_separator);
+	ClassDB::bind_method(D_METHOD("global_menu_remove_item", "menu", "idx"), &_OS::global_menu_remove_item);
+	ClassDB::bind_method(D_METHOD("global_menu_clear", "menu"), &_OS::global_menu_clear);
+
 	ClassDB::bind_method(D_METHOD("get_video_driver_count"), &_OS::get_video_driver_count);
 	ClassDB::bind_method(D_METHOD("get_video_driver_count"), &_OS::get_video_driver_count);
 	ClassDB::bind_method(D_METHOD("get_video_driver_name", "driver"), &_OS::get_video_driver_name);
 	ClassDB::bind_method(D_METHOD("get_video_driver_name", "driver"), &_OS::get_video_driver_name);
 	ClassDB::bind_method(D_METHOD("get_current_video_driver"), &_OS::get_current_video_driver);
 	ClassDB::bind_method(D_METHOD("get_current_video_driver"), &_OS::get_current_video_driver);

+ 5 - 0
core/bind/core_bind.h

@@ -143,6 +143,11 @@ public:
 		MONTH_DECEMBER
 		MONTH_DECEMBER
 	};
 	};
 
 
+	void global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta);
+	void global_menu_add_separator(const String &p_menu);
+	void global_menu_remove_item(const String &p_menu, int p_idx);
+	void global_menu_clear(const String &p_menu);
+
 	Point2 get_mouse_position() const;
 	Point2 get_mouse_position() const;
 	void set_window_title(const String &p_title);
 	void set_window_title(const String &p_title);
 	int get_mouse_button_state() const;
 	int get_mouse_button_state() const;

+ 8 - 0
core/os/main_loop.cpp

@@ -49,6 +49,8 @@ void MainLoop::_bind_methods() {
 	BIND_VMETHOD(MethodInfo("_drop_files", PropertyInfo(Variant::POOL_STRING_ARRAY, "files"), PropertyInfo(Variant::INT, "from_screen")));
 	BIND_VMETHOD(MethodInfo("_drop_files", PropertyInfo(Variant::POOL_STRING_ARRAY, "files"), PropertyInfo(Variant::INT, "from_screen")));
 	BIND_VMETHOD(MethodInfo("_finalize"));
 	BIND_VMETHOD(MethodInfo("_finalize"));
 
 
+	BIND_VMETHOD(MethodInfo("_global_menu_action", PropertyInfo(Variant::NIL, "id"), PropertyInfo(Variant::NIL, "meta")));
+
 	BIND_CONSTANT(NOTIFICATION_WM_MOUSE_ENTER);
 	BIND_CONSTANT(NOTIFICATION_WM_MOUSE_ENTER);
 	BIND_CONSTANT(NOTIFICATION_WM_MOUSE_EXIT);
 	BIND_CONSTANT(NOTIFICATION_WM_MOUSE_EXIT);
 	BIND_CONSTANT(NOTIFICATION_WM_FOCUS_IN);
 	BIND_CONSTANT(NOTIFICATION_WM_FOCUS_IN);
@@ -115,6 +117,12 @@ void MainLoop::drop_files(const Vector<String> &p_files, int p_from_screen) {
 		get_script_instance()->call("_drop_files", p_files, p_from_screen);
 		get_script_instance()->call("_drop_files", p_files, p_from_screen);
 }
 }
 
 
+void MainLoop::global_menu_action(const Variant &p_id, const Variant &p_meta) {
+
+	if (get_script_instance())
+		get_script_instance()->call("_global_menu_action", p_id, p_meta);
+}
+
 void MainLoop::finish() {
 void MainLoop::finish() {
 
 
 	if (get_script_instance()) {
 	if (get_script_instance()) {

+ 1 - 0
core/os/main_loop.h

@@ -71,6 +71,7 @@ public:
 	virtual void finish();
 	virtual void finish();
 
 
 	virtual void drop_files(const Vector<String> &p_files, int p_from_screen = 0);
 	virtual void drop_files(const Vector<String> &p_files, int p_from_screen = 0);
+	virtual void global_menu_action(const Variant &p_id, const Variant &p_meta);
 
 
 	void set_init_script(const Ref<Script> &p_init_script);
 	void set_init_script(const Ref<Script> &p_init_script);
 
 

+ 5 - 0
core/os/os.h

@@ -143,6 +143,11 @@ public:
 
 
 	static OS *get_singleton();
 	static OS *get_singleton();
 
 
+	virtual void global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta){};
+	virtual void global_menu_add_separator(const String &p_menu){};
+	virtual void global_menu_remove_item(const String &p_menu, int p_idx){};
+	virtual void global_menu_clear(const String &p_menu){};
+
 	void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, Logger::ErrorType p_type = Logger::ERR_ERROR);
 	void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, Logger::ErrorType p_type = Logger::ERR_ERROR);
 	void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
 	void print(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
 	void printerr(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;
 	void printerr(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3;

+ 10 - 0
doc/classes/MainLoop.xml

@@ -61,6 +61,16 @@
 				Called before the program exits.
 				Called before the program exits.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="_global_menu_action" qualifiers="virtual">
+			<return type="void">
+			</return>
+			<argument index="0" name="id" type="Variant">
+			</argument>
+			<argument index="1" name="meta" type="Variant">
+			</argument>
+			<description>
+			</description>
+		</method>
 		<method name="_idle" qualifiers="virtual">
 		<method name="_idle" qualifiers="virtual">
 			<return type="bool">
 			<return type="bool">
 			</return>
 			</return>

+ 44 - 0
doc/classes/OS.xml

@@ -497,6 +497,50 @@
 				Returns unobscured area of the window where interactive controls should be rendered.
 				Returns unobscured area of the window where interactive controls should be rendered.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="global_menu_add_item">
+			<return type="void">
+			</return>
+			<argument index="0" name="menu" type="String">
+			</argument>
+			<argument index="1" name="label" type="String">
+			</argument>
+			<argument index="2" name="id" type="Variant">
+			</argument>
+			<argument index="3" name="meta" type="Variant">
+			</argument>
+			<description>
+				Add a new item with text "label" to global menu. Use "_dock" menu to add item to the macOS dock icon menu.
+			</description>
+		</method>
+		<method name="global_menu_add_separator">
+			<return type="void">
+			</return>
+			<argument index="0" name="menu" type="String">
+			</argument>
+			<description>
+				Add a separator between items. Separators also occupy an index.
+			</description>
+		</method>
+		<method name="global_menu_clear">
+			<return type="void">
+			</return>
+			<argument index="0" name="menu" type="String">
+			</argument>
+			<description>
+				Clear the global menu, in effect removing all items.
+			</description>
+		</method>
+		<method name="global_menu_remove_item">
+			<return type="void">
+			</return>
+			<argument index="0" name="menu" type="String">
+			</argument>
+			<argument index="1" name="idx" type="int">
+			</argument>
+			<description>
+				Removes the item at index "idx" from the global menu. Note that the indexes of items after the removed item are going to be shifted by one.
+			</description>
+		</method>
 		<method name="has_environment" qualifiers="const">
 		<method name="has_environment" qualifiers="const">
 			<return type="bool">
 			<return type="bool">
 			</return>
 			</return>

+ 9 - 0
doc/classes/SceneTree.xml

@@ -324,6 +324,15 @@
 				Emitted when files are dragged from the OS file manager and dropped in the game window. The arguments are a list of file paths and the identifier of the screen where the drag originated.
 				Emitted when files are dragged from the OS file manager and dropped in the game window. The arguments are a list of file paths and the identifier of the screen where the drag originated.
 			</description>
 			</description>
 		</signal>
 		</signal>
+		<signal name="global_menu_action">
+			<argument index="0" name="id" type="Nil">
+			</argument>
+			<argument index="1" name="meta" type="Nil">
+			</argument>
+			<description>
+				Emitted whenever global menu item is clicked.
+			</description>
+		</signal>
 		<signal name="idle_frame">
 		<signal name="idle_frame">
 			<description>
 			<description>
 				Emitted immediately before [method Node._process] is called on every node in the [SceneTree].
 				Emitted immediately before [method Node._process] is called on every node in the [SceneTree].

+ 26 - 0
editor/editor_node.cpp

@@ -135,6 +135,8 @@ void EditorNode::_update_scene_tabs() {
 
 
 	bool show_rb = EditorSettings::get_singleton()->get("interface/scene_tabs/show_script_button");
 	bool show_rb = EditorSettings::get_singleton()->get("interface/scene_tabs/show_script_button");
 
 
+	OS::get_singleton()->global_menu_clear("_dock");
+
 	scene_tabs->clear_tabs();
 	scene_tabs->clear_tabs();
 	Ref<Texture> script_icon = gui_base->get_icon("Script", "EditorIcons");
 	Ref<Texture> script_icon = gui_base->get_icon("Script", "EditorIcons");
 	for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
 	for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
@@ -149,11 +151,16 @@ void EditorNode::_update_scene_tabs() {
 		bool unsaved = (i == current) ? saved_version != editor_data.get_undo_redo().get_version() : editor_data.get_scene_version(i) != 0;
 		bool unsaved = (i == current) ? saved_version != editor_data.get_undo_redo().get_version() : editor_data.get_scene_version(i) != 0;
 		scene_tabs->add_tab(editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), icon);
 		scene_tabs->add_tab(editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), icon);
 
 
+		OS::get_singleton()->global_menu_add_item("_dock", editor_data.get_scene_title(i) + (unsaved ? "(*)" : ""), GLOBAL_SCENE, i);
+
 		if (show_rb && editor_data.get_scene_root_script(i).is_valid()) {
 		if (show_rb && editor_data.get_scene_root_script(i).is_valid()) {
 			scene_tabs->set_tab_right_button(i, script_icon);
 			scene_tabs->set_tab_right_button(i, script_icon);
 		}
 		}
 	}
 	}
 
 
+	OS::get_singleton()->global_menu_add_separator("_dock");
+	OS::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), GLOBAL_NEW_WINDOW, Variant());
+
 	scene_tabs->set_current_tab(editor_data.get_edited_scene());
 	scene_tabs->set_current_tab(editor_data.get_edited_scene());
 
 
 	if (scene_tabs->get_offset_buttons_visible()) {
 	if (scene_tabs->get_offset_buttons_visible()) {
@@ -290,6 +297,7 @@ void EditorNode::_notification(int p_what) {
 			get_tree()->get_root()->set_as_audio_listener_2d(false);
 			get_tree()->get_root()->set_as_audio_listener_2d(false);
 			get_tree()->set_auto_accept_quit(false);
 			get_tree()->set_auto_accept_quit(false);
 			get_tree()->connect("files_dropped", this, "_dropped_files");
 			get_tree()->connect("files_dropped", this, "_dropped_files");
+			get_tree()->connect("global_menu_action", this, "_global_menu_action");
 
 
 			/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
 			/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
 		} break;
 		} break;
@@ -4941,6 +4949,23 @@ void EditorNode::remove_tool_menu_item(const String &p_name) {
 	}
 	}
 }
 }
 
 
+void EditorNode::_global_menu_action(const Variant &p_id, const Variant &p_meta) {
+
+	int id = (int)p_id;
+	if (id == GLOBAL_NEW_WINDOW) {
+		if (OS::get_singleton()->get_main_loop()) {
+			List<String> args;
+			String exec = OS::get_singleton()->get_executable_path();
+
+			OS::ProcessID pid = 0;
+			OS::get_singleton()->execute(exec, args, false, &pid);
+		}
+	} else if (id == GLOBAL_SCENE) {
+		int idx = (int)p_meta;
+		scene_tabs->set_current_tab(idx);
+	}
+}
+
 void EditorNode::_dropped_files(const Vector<String> &p_files, int p_screen) {
 void EditorNode::_dropped_files(const Vector<String> &p_files, int p_screen) {
 
 
 	String to_path = ProjectSettings::get_singleton()->globalize_path(get_filesystem_dock()->get_selected_path());
 	String to_path = ProjectSettings::get_singleton()->globalize_path(get_filesystem_dock()->get_selected_path());
@@ -5322,6 +5347,7 @@ void EditorNode::_bind_methods() {
 
 
 	ClassDB::bind_method("_clear_undo_history", &EditorNode::_clear_undo_history);
 	ClassDB::bind_method("_clear_undo_history", &EditorNode::_clear_undo_history);
 	ClassDB::bind_method("_dropped_files", &EditorNode::_dropped_files);
 	ClassDB::bind_method("_dropped_files", &EditorNode::_dropped_files);
+	ClassDB::bind_method(D_METHOD("_global_menu_action"), &EditorNode::_global_menu_action, DEFVAL(Variant()));
 	ClassDB::bind_method("_toggle_distraction_free_mode", &EditorNode::_toggle_distraction_free_mode);
 	ClassDB::bind_method("_toggle_distraction_free_mode", &EditorNode::_toggle_distraction_free_mode);
 	ClassDB::bind_method("edit_item_resource", &EditorNode::edit_item_resource);
 	ClassDB::bind_method("edit_item_resource", &EditorNode::edit_item_resource);
 
 

+ 4 - 0
editor/editor_node.h

@@ -207,6 +207,9 @@ private:
 
 
 		SET_VIDEO_DRIVER_SAVE_AND_RESTART,
 		SET_VIDEO_DRIVER_SAVE_AND_RESTART,
 
 
+		GLOBAL_NEW_WINDOW,
+		GLOBAL_SCENE,
+
 		IMPORT_PLUGIN_BASE = 100,
 		IMPORT_PLUGIN_BASE = 100,
 
 
 		TOOL_MENU_BASE = 1000
 		TOOL_MENU_BASE = 1000
@@ -504,6 +507,7 @@ private:
 	void _add_to_recent_scenes(const String &p_scene);
 	void _add_to_recent_scenes(const String &p_scene);
 	void _update_recent_scenes();
 	void _update_recent_scenes();
 	void _open_recent_scene(int p_idx);
 	void _open_recent_scene(int p_idx);
+	void _global_menu_action(const Variant &p_id, const Variant &p_meta);
 	void _dropped_files(const Vector<String> &p_files, int p_screen);
 	void _dropped_files(const Vector<String> &p_files, int p_screen);
 	void _add_dropped_files_recursive(const Vector<String> &p_files, String to_path);
 	void _add_dropped_files_recursive(const Vector<String> &p_files, String to_path);
 	String _recent_scene;
 	String _recent_scene;

+ 35 - 0
editor/project_manager.cpp

@@ -946,6 +946,11 @@ public:
 	static const char *SIGNAL_SELECTION_CHANGED;
 	static const char *SIGNAL_SELECTION_CHANGED;
 	static const char *SIGNAL_PROJECT_ASK_OPEN;
 	static const char *SIGNAL_PROJECT_ASK_OPEN;
 
 
+	enum MenuOptions {
+		GLOBAL_NEW_WINDOW,
+		GLOBAL_OPEN_PROJECT
+	};
+
 	// Can often be passed by copy
 	// Can often be passed by copy
 	struct Item {
 	struct Item {
 		String project_key;
 		String project_key;
@@ -1181,6 +1186,7 @@ void ProjectList::load_projects() {
 	_projects.clear();
 	_projects.clear();
 	_last_clicked = "";
 	_last_clicked = "";
 	_selected_project_keys.clear();
 	_selected_project_keys.clear();
+	OS::get_singleton()->global_menu_clear("_dock");
 
 
 	// Load data
 	// Load data
 	// TODO Would be nice to change how projects and favourites are stored... it complicates things a bit.
 	// TODO Would be nice to change how projects and favourites are stored... it complicates things a bit.
@@ -1218,6 +1224,9 @@ void ProjectList::load_projects() {
 		create_project_item_control(i);
 		create_project_item_control(i);
 	}
 	}
 
 
+	OS::get_singleton()->global_menu_add_separator("_dock");
+	OS::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), GLOBAL_NEW_WINDOW, Variant());
+
 	sort_projects();
 	sort_projects();
 
 
 	set_v_scroll(0);
 	set_v_scroll(0);
@@ -1305,6 +1314,7 @@ void ProjectList::create_project_item_control(int p_index) {
 	fpath->set_clip_text(true);
 	fpath->set_clip_text(true);
 
 
 	_scroll_children->add_child(hb);
 	_scroll_children->add_child(hb);
+	OS::get_singleton()->global_menu_add_item("_dock", item.project_name + " ( " + item.path + " )", GLOBAL_OPEN_PROJECT, Variant(item.path.plus_file("project.godot")));
 	item.control = hb;
 	item.control = hb;
 }
 }
 
 
@@ -1894,6 +1904,29 @@ void ProjectManager::_confirm_update_settings() {
 	_open_selected_projects();
 	_open_selected_projects();
 }
 }
 
 
+void ProjectManager::_global_menu_action(const Variant &p_id, const Variant &p_meta) {
+
+	int id = (int)p_id;
+	if (id == ProjectList::GLOBAL_NEW_WINDOW) {
+		List<String> args;
+		String exec = OS::get_singleton()->get_executable_path();
+
+		OS::ProcessID pid = 0;
+		OS::get_singleton()->execute(exec, args, false, &pid);
+	} else if (id == ProjectList::GLOBAL_OPEN_PROJECT) {
+		String conf = (String)p_meta;
+
+		if (conf != String()) {
+			List<String> args;
+			args.push_back(conf);
+			String exec = OS::get_singleton()->get_executable_path();
+
+			OS::ProcessID pid = 0;
+			OS::get_singleton()->execute(exec, args, false, &pid);
+		}
+	}
+}
+
 void ProjectManager::_open_selected_projects() {
 void ProjectManager::_open_selected_projects() {
 
 
 	const Set<String> &selected_list = _project_list->get_selected_project_keys();
 	const Set<String> &selected_list = _project_list->get_selected_project_keys();
@@ -2236,6 +2269,7 @@ void ProjectManager::_bind_methods() {
 
 
 	ClassDB::bind_method("_open_selected_projects_ask", &ProjectManager::_open_selected_projects_ask);
 	ClassDB::bind_method("_open_selected_projects_ask", &ProjectManager::_open_selected_projects_ask);
 	ClassDB::bind_method("_open_selected_projects", &ProjectManager::_open_selected_projects);
 	ClassDB::bind_method("_open_selected_projects", &ProjectManager::_open_selected_projects);
+	ClassDB::bind_method(D_METHOD("_global_menu_action"), &ProjectManager::_global_menu_action, DEFVAL(Variant()));
 	ClassDB::bind_method("_run_project", &ProjectManager::_run_project);
 	ClassDB::bind_method("_run_project", &ProjectManager::_run_project);
 	ClassDB::bind_method("_run_project_confirm", &ProjectManager::_run_project_confirm);
 	ClassDB::bind_method("_run_project_confirm", &ProjectManager::_run_project_confirm);
 	ClassDB::bind_method("_scan_projects", &ProjectManager::_scan_projects);
 	ClassDB::bind_method("_scan_projects", &ProjectManager::_scan_projects);
@@ -2561,6 +2595,7 @@ ProjectManager::ProjectManager() {
 	}
 	}
 
 
 	SceneTree::get_singleton()->connect("files_dropped", this, "_files_dropped");
 	SceneTree::get_singleton()->connect("files_dropped", this, "_files_dropped");
+	SceneTree::get_singleton()->connect("global_menu_action", this, "_global_menu_action");
 
 
 	run_error_diag = memnew(AcceptDialog);
 	run_error_diag = memnew(AcceptDialog);
 	gui_base->add_child(run_error_diag);
 	gui_base->add_child(run_error_diag);

+ 2 - 0
editor/project_manager.h

@@ -43,6 +43,7 @@ class ProjectList;
 class ProjectListFilter;
 class ProjectListFilter;
 
 
 class ProjectManager : public Control {
 class ProjectManager : public Control {
+
 	GDCLASS(ProjectManager, Control);
 	GDCLASS(ProjectManager, Control);
 
 
 	Button *erase_btn;
 	Button *erase_btn;
@@ -96,6 +97,7 @@ class ProjectManager : public Control {
 	void _restart_confirm();
 	void _restart_confirm();
 	void _exit_dialog();
 	void _exit_dialog();
 	void _scan_begin(const String &p_base);
 	void _scan_begin(const String &p_base);
+	void _global_menu_action(const Variant &p_id, const Variant &p_meta);
 
 
 	void _confirm_update_settings();
 	void _confirm_update_settings();
 
 

+ 25 - 0
platform/osx/os_osx.h

@@ -157,6 +157,26 @@ public:
 	int video_driver_index;
 	int video_driver_index;
 	virtual int get_current_video_driver() const;
 	virtual int get_current_video_driver() const;
 
 
+	struct GlobalMenuItem {
+		String label;
+		Variant signal;
+		Variant meta;
+
+		GlobalMenuItem() {
+			//NOP
+		}
+
+		GlobalMenuItem(const String &p_label, const Variant &p_signal, const Variant &p_meta) {
+			label = p_label;
+			signal = p_signal;
+			meta = p_meta;
+		}
+	};
+
+	Map<String, Vector<GlobalMenuItem> > global_menus;
+
+	void _update_global_menu();
+
 protected:
 protected:
 	virtual void initialize_core();
 	virtual void initialize_core();
 	virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
 	virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
@@ -168,6 +188,11 @@ protected:
 public:
 public:
 	static OS_OSX *singleton;
 	static OS_OSX *singleton;
 
 
+	void global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta);
+	void global_menu_add_separator(const String &p_menu);
+	void global_menu_remove_item(const String &p_menu, int p_idx);
+	void global_menu_clear(const String &p_menu);
+
 	void wm_minimized(bool p_minimized);
 	void wm_minimized(bool p_minimized);
 
 
 	virtual String get_name() const;
 	virtual String get_name() const;

+ 93 - 1
platform/osx/os_osx.mm

@@ -204,11 +204,53 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
 	}
 	}
 }
 }
 
 
+- (void)globalMenuCallback:(id)sender {
+
+	if (![sender representedObject])
+		return;
+
+	OS_OSX::GlobalMenuItem *item = (OS_OSX::GlobalMenuItem *)[[sender representedObject] pointerValue];
+
+	if (!item)
+		return;
+
+	OS_OSX::singleton->main_loop->global_menu_action(item->signal, item->meta);
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender {
+
+	NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+
+	Vector<OS_OSX::GlobalMenuItem> &E = OS_OSX::singleton->global_menus["_dock"];
+	for (int i = 0; i < E.size(); i++) {
+		if (E[i].label == String()) {
+			[menu addItem:[NSMenuItem separatorItem]];
+		} else {
+			NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:E[i].label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
+			[menu_item setRepresentedObject:[NSValue valueWithPointer:&(E[i])]];
+		}
+	}
+
+	return menu;
+}
+
 - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
 - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
-	// Note: called before main loop init!
+	// Note: may be called called before main loop init!
 	char *utfs = strdup([filename UTF8String]);
 	char *utfs = strdup([filename UTF8String]);
 	OS_OSX::singleton->open_with_filename.parse_utf8(utfs);
 	OS_OSX::singleton->open_with_filename.parse_utf8(utfs);
 	free(utfs);
 	free(utfs);
+
+#ifdef TOOLS_ENABLED
+	// Open new instance
+	if (OS_OSX::singleton->get_main_loop()) {
+		List<String> args;
+		args.push_back(OS_OSX::singleton->open_with_filename);
+		String exec = OS::get_singleton()->get_executable_path();
+
+		OS::ProcessID pid = 0;
+		OS::get_singleton()->execute(exec, args, false, &pid);
+	}
+#endif
 	return YES;
 	return YES;
 }
 }
 
 
@@ -1266,6 +1308,56 @@ inline void sendPanEvent(double dx, double dy, int modifierFlags) {
 
 
 @end
 @end
 
 
+void OS_OSX::_update_global_menu() {
+
+	NSMenu *main_menu = [NSApp mainMenu];
+
+	for (int i = 1; i < [main_menu numberOfItems]; i++) {
+		[main_menu removeItemAtIndex:i];
+	}
+	for (Map<String, Vector<GlobalMenuItem> >::Element *E = global_menus.front(); E; E = E->next()) {
+		if (E->key() != "_dock") {
+			NSMenu *menu = [[[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()]] autorelease];
+			for (int i = 0; i < E->get().size(); i++) {
+				if (E->get()[i].label == String()) {
+					[menu addItem:[NSMenuItem separatorItem]];
+				} else {
+					NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:E->get()[i].label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""];
+					[menu_item setRepresentedObject:[NSValue valueWithPointer:&(E->get()[i])]];
+				}
+			}
+			NSMenuItem *menu_item = [main_menu addItemWithTitle:[NSString stringWithUTF8String:E->key().utf8().get_data()] action:nil keyEquivalent:@""];
+			[main_menu setSubmenu:menu forItem:menu_item];
+		}
+	}
+}
+
+void OS_OSX::global_menu_add_item(const String &p_menu, const String &p_label, const Variant &p_signal, const Variant &p_meta) {
+
+	global_menus[p_menu].push_back(GlobalMenuItem(p_label, p_signal, p_meta));
+	_update_global_menu();
+}
+
+void OS_OSX::global_menu_add_separator(const String &p_menu) {
+
+	global_menus[p_menu].push_back(GlobalMenuItem());
+	_update_global_menu();
+}
+
+void OS_OSX::global_menu_remove_item(const String &p_menu, int p_idx) {
+
+	ERR_FAIL_INDEX(p_idx, global_menus[p_menu].size());
+
+	global_menus[p_menu].remove(p_idx);
+	_update_global_menu();
+}
+
+void OS_OSX::global_menu_clear(const String &p_menu) {
+
+	global_menus[p_menu].clear();
+	_update_global_menu();
+}
+
 Point2 OS_OSX::get_ime_selection() const {
 Point2 OS_OSX::get_ime_selection() const {
 
 
 	return im_selection;
 	return im_selection;

+ 7 - 0
scene/main/scene_tree.cpp

@@ -1673,6 +1673,12 @@ void SceneTree::drop_files(const Vector<String> &p_files, int p_from_screen) {
 	MainLoop::drop_files(p_files, p_from_screen);
 	MainLoop::drop_files(p_files, p_from_screen);
 }
 }
 
 
+void SceneTree::global_menu_action(const Variant &p_id, const Variant &p_meta) {
+
+	emit_signal("global_menu_action", p_id, p_meta);
+	MainLoop::global_menu_action(p_id, p_meta);
+}
+
 Ref<SceneTreeTimer> SceneTree::create_timer(float p_delay_sec, bool p_process_pause) {
 Ref<SceneTreeTimer> SceneTree::create_timer(float p_delay_sec, bool p_process_pause) {
 
 
 	Ref<SceneTreeTimer> stt;
 	Ref<SceneTreeTimer> stt;
@@ -1894,6 +1900,7 @@ void SceneTree::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("physics_frame"));
 	ADD_SIGNAL(MethodInfo("physics_frame"));
 
 
 	ADD_SIGNAL(MethodInfo("files_dropped", PropertyInfo(Variant::POOL_STRING_ARRAY, "files"), PropertyInfo(Variant::INT, "screen")));
 	ADD_SIGNAL(MethodInfo("files_dropped", PropertyInfo(Variant::POOL_STRING_ARRAY, "files"), PropertyInfo(Variant::INT, "screen")));
+	ADD_SIGNAL(MethodInfo("global_menu_action", PropertyInfo(Variant::NIL, "id"), PropertyInfo(Variant::NIL, "meta")));
 	ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id")));
 	ADD_SIGNAL(MethodInfo("network_peer_connected", PropertyInfo(Variant::INT, "id")));
 	ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id")));
 	ADD_SIGNAL(MethodInfo("network_peer_disconnected", PropertyInfo(Variant::INT, "id")));
 	ADD_SIGNAL(MethodInfo("connected_to_server"));
 	ADD_SIGNAL(MethodInfo("connected_to_server"));

+ 1 - 0
scene/main/scene_tree.h

@@ -407,6 +407,7 @@ public:
 	static SceneTree *get_singleton() { return singleton; }
 	static SceneTree *get_singleton() { return singleton; }
 
 
 	void drop_files(const Vector<String> &p_files, int p_from_screen = 0);
 	void drop_files(const Vector<String> &p_files, int p_from_screen = 0);
+	void global_menu_action(const Variant &p_id, const Variant &p_meta);
 
 
 	//network API
 	//network API