| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  | #include <AK/HashMap.h>
 | 
					
						
							| 
									
										
										
										
											2019-02-12 14:09:48 +01:00
										 |  |  | #include <LibGUI/GAction.h>
 | 
					
						
							| 
									
										
										
										
											2019-02-13 18:48:22 +01:00
										 |  |  | #include <LibGUI/GEventLoop.h>
 | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  | #include <LibGUI/GMenu.h>
 | 
					
						
							| 
									
										
										
										
											2019-02-12 10:08:35 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-26 01:07:30 +02:00
										 |  |  | //#define GMENU_DEBUG
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-12 10:08:35 +01:00
										 |  |  | static HashMap<int, GMenu*>& all_menus() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     static HashMap<int, GMenu*>* map; | 
					
						
							|  |  |  |     if (!map) | 
					
						
							|  |  |  |         map = new HashMap<int, GMenu*>(); | 
					
						
							|  |  |  |     return *map; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | GMenu* GMenu::from_menu_id(int menu_id) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     auto it = all_menus().find(menu_id); | 
					
						
							|  |  |  |     if (it == all_menus().end()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							|  |  |  |     return (*it).value; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-02-11 14:43:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-02 14:58:02 +02:00
										 |  |  | GMenu::GMenu(const StringView& name) | 
					
						
							| 
									
										
										
										
											2019-02-11 15:37:12 +01:00
										 |  |  |     : m_name(name) | 
					
						
							| 
									
										
										
										
											2019-02-11 14:43:43 +01:00
										 |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | GMenu::~GMenu() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-02-13 18:48:22 +01:00
										 |  |  |     unrealize_menu(); | 
					
						
							| 
									
										
										
										
											2019-02-11 14:43:43 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-21 18:37:47 +02:00
										 |  |  | void GMenu::add_action(NonnullRefPtr<GAction> action) | 
					
						
							| 
									
										
										
										
											2019-02-11 15:37:12 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-04-12 02:53:27 +02:00
										 |  |  |     m_items.append(make<GMenuItem>(m_menu_id, move(action))); | 
					
						
							| 
									
										
										
										
											2019-05-26 01:07:30 +02:00
										 |  |  | #ifdef GMENU_DEBUG
 | 
					
						
							|  |  |  |     dbgprintf("GMenu::add_action(): MenuItem Menu ID: %d\n", m_menu_id); | 
					
						
							|  |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2019-02-11 15:37:12 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-02-11 14:43:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-28 21:11:53 +02:00
										 |  |  | void GMenu::add_submenu(NonnullOwnPtr<GMenu> submenu) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_items.append(make<GMenuItem>(m_menu_id, move(submenu))); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-11 15:37:12 +01:00
										 |  |  | void GMenu::add_separator() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-04-12 02:53:27 +02:00
										 |  |  |     m_items.append(make<GMenuItem>(m_menu_id, GMenuItem::Separator)); | 
					
						
							| 
									
										
										
										
											2019-02-11 15:37:12 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-02-12 00:52:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-28 21:11:53 +02:00
										 |  |  | void GMenu::realize_if_needed() | 
					
						
							| 
									
										
										
										
											2019-04-12 17:10:30 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-05-26 01:07:30 +02:00
										 |  |  |     if (m_menu_id == -1) | 
					
						
							| 
									
										
										
										
											2019-04-12 17:10:30 +02:00
										 |  |  |         realize_menu(); | 
					
						
							| 
									
										
										
										
											2019-08-28 21:11:53 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void GMenu::popup(const Point& screen_position) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     realize_if_needed(); | 
					
						
							| 
									
										
										
										
											2019-04-12 17:10:30 +02:00
										 |  |  |     WSAPI_ClientMessage request; | 
					
						
							|  |  |  |     request.type = WSAPI_ClientMessage::Type::PopupMenu; | 
					
						
							|  |  |  |     request.menu.menu_id = m_menu_id; | 
					
						
							|  |  |  |     request.menu.position = screen_position; | 
					
						
							| 
									
										
										
										
											2019-07-17 20:57:27 +02:00
										 |  |  |     GWindowServerConnection::the().post_message_to_server(request); | 
					
						
							| 
									
										
										
										
											2019-04-12 17:10:30 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-14 01:53:19 +02:00
										 |  |  | void GMenu::dismiss() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-05-26 01:07:30 +02:00
										 |  |  |     if (m_menu_id == -1) | 
					
						
							| 
									
										
										
										
											2019-04-14 01:53:19 +02:00
										 |  |  |         return; | 
					
						
							|  |  |  |     WSAPI_ClientMessage request; | 
					
						
							|  |  |  |     request.type = WSAPI_ClientMessage::Type::DismissMenu; | 
					
						
							|  |  |  |     request.menu.menu_id = m_menu_id; | 
					
						
							| 
									
										
										
										
											2019-07-17 20:57:27 +02:00
										 |  |  |     GWindowServerConnection::the().post_message_to_server(request); | 
					
						
							| 
									
										
										
										
											2019-04-14 01:53:19 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-12 00:52:19 +01:00
										 |  |  | int GMenu::realize_menu() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-02-15 09:17:18 +01:00
										 |  |  |     WSAPI_ClientMessage request; | 
					
						
							|  |  |  |     request.type = WSAPI_ClientMessage::Type::CreateMenu; | 
					
						
							| 
									
										
										
										
											2019-02-25 22:06:55 +01:00
										 |  |  |     ASSERT(m_name.length() < (ssize_t)sizeof(request.text)); | 
					
						
							| 
									
										
										
										
											2019-02-14 01:21:32 +01:00
										 |  |  |     strcpy(request.text, m_name.characters()); | 
					
						
							|  |  |  |     request.text_length = m_name.length(); | 
					
						
							| 
									
										
										
										
											2019-07-17 20:57:27 +02:00
										 |  |  |     auto response = GWindowServerConnection::the().sync_request(request, WSAPI_ServerMessage::Type::DidCreateMenu); | 
					
						
							| 
									
										
										
										
											2019-02-13 18:48:22 +01:00
										 |  |  |     m_menu_id = response.menu.menu_id; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-26 01:07:30 +02:00
										 |  |  | #ifdef GMENU_DEBUG
 | 
					
						
							|  |  |  |     dbgprintf("GMenu::realize_menu(): New menu ID: %d\n", m_menu_id); | 
					
						
							|  |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2019-02-12 00:52:19 +01:00
										 |  |  |     ASSERT(m_menu_id > 0); | 
					
						
							| 
									
										
										
										
											2019-02-25 22:06:55 +01:00
										 |  |  |     for (int i = 0; i < m_items.size(); ++i) { | 
					
						
							| 
									
										
										
										
											2019-07-24 09:12:23 +02:00
										 |  |  |         auto& item = m_items[i]; | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  |         item.set_menu_id({}, m_menu_id); | 
					
						
							|  |  |  |         item.set_identifier({}, i); | 
					
						
							| 
									
										
										
										
											2019-02-12 14:09:48 +01:00
										 |  |  |         if (item.type() == GMenuItem::Separator) { | 
					
						
							| 
									
										
										
										
											2019-02-15 09:17:18 +01:00
										 |  |  |             WSAPI_ClientMessage request; | 
					
						
							|  |  |  |             request.type = WSAPI_ClientMessage::Type::AddMenuSeparator; | 
					
						
							| 
									
										
										
										
											2019-02-13 21:47:14 +01:00
										 |  |  |             request.menu.menu_id = m_menu_id; | 
					
						
							| 
									
										
										
										
											2019-08-28 21:11:53 +02:00
										 |  |  |             request.menu.submenu_id = -1; | 
					
						
							| 
									
										
										
										
											2019-07-17 20:57:27 +02:00
										 |  |  |             GWindowServerConnection::the().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuSeparator); | 
					
						
							| 
									
										
										
										
											2019-02-12 14:09:48 +01:00
										 |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-08-28 21:11:53 +02:00
										 |  |  |         if (item.type() == GMenuItem::Submenu) { | 
					
						
							|  |  |  |             auto& submenu = *item.submenu(); | 
					
						
							|  |  |  |             submenu.realize_if_needed(); | 
					
						
							|  |  |  |             WSAPI_ClientMessage request; | 
					
						
							|  |  |  |             request.type = WSAPI_ClientMessage::Type::AddMenuItem; | 
					
						
							|  |  |  |             request.menu.menu_id = m_menu_id; | 
					
						
							|  |  |  |             request.menu.submenu_id = submenu.menu_id(); | 
					
						
							|  |  |  |             request.menu.identifier = i; | 
					
						
							|  |  |  |             // FIXME: It should be possible to disable a submenu.
 | 
					
						
							|  |  |  |             request.menu.enabled = true; | 
					
						
							|  |  |  |             request.menu.checkable = false; | 
					
						
							|  |  |  |             request.menu.checked = false; | 
					
						
							| 
									
										
										
										
											2019-10-12 18:02:39 -05:00
										 |  |  |              | 
					
						
							|  |  |  |             // no shortcut on submenu, make sure this is cleared out
 | 
					
						
							|  |  |  |             request.menu.shortcut_text_length = 0; | 
					
						
							|  |  |  |             strcpy(request.menu.shortcut_text, "\0"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-28 21:11:53 +02:00
										 |  |  |             ASSERT(submenu.name().length() < (ssize_t)sizeof(request.text)); | 
					
						
							|  |  |  |             strcpy(request.text, submenu.name().characters()); | 
					
						
							|  |  |  |             request.text_length = submenu.name().length(); | 
					
						
							|  |  |  |             GWindowServerConnection::the().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuItem); | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-02-12 14:09:48 +01:00
										 |  |  |         if (item.type() == GMenuItem::Action) { | 
					
						
							|  |  |  |             auto& action = *item.action(); | 
					
						
							| 
									
										
										
										
											2019-02-15 09:17:18 +01:00
										 |  |  |             WSAPI_ClientMessage request; | 
					
						
							|  |  |  |             request.type = WSAPI_ClientMessage::Type::AddMenuItem; | 
					
						
							| 
									
										
										
										
											2019-02-13 21:47:14 +01:00
										 |  |  |             request.menu.menu_id = m_menu_id; | 
					
						
							| 
									
										
										
										
											2019-08-28 21:11:53 +02:00
										 |  |  |             request.menu.submenu_id = -1; | 
					
						
							| 
									
										
										
										
											2019-02-13 21:47:14 +01:00
										 |  |  |             request.menu.identifier = i; | 
					
						
							| 
									
										
										
										
											2019-04-12 02:53:27 +02:00
										 |  |  |             request.menu.enabled = action.is_enabled(); | 
					
						
							| 
									
										
										
										
											2019-04-26 21:09:56 +02:00
										 |  |  |             request.menu.checkable = action.is_checkable(); | 
					
						
							| 
									
										
										
										
											2019-08-26 18:54:44 +02:00
										 |  |  |             if (action.icon()) { | 
					
						
							|  |  |  |                 ASSERT(action.icon()->format() == GraphicsBitmap::Format::RGBA32); | 
					
						
							|  |  |  |                 ASSERT(action.icon()->size() == Size(16, 16)); | 
					
						
							|  |  |  |                 if (action.icon()->shared_buffer_id() == -1) { | 
					
						
							|  |  |  |                     auto shared_buffer = SharedBuffer::create_with_size(action.icon()->size_in_bytes()); | 
					
						
							|  |  |  |                     ASSERT(shared_buffer); | 
					
						
							|  |  |  |                     auto shared_icon = GraphicsBitmap::create_with_shared_buffer(GraphicsBitmap::Format::RGBA32, *shared_buffer, action.icon()->size()); | 
					
						
							|  |  |  |                     memcpy(shared_buffer->data(), action.icon()->bits(0), action.icon()->size_in_bytes()); | 
					
						
							|  |  |  |                     shared_buffer->seal(); | 
					
						
							|  |  |  |                     shared_buffer->share_with(GWindowServerConnection::the().server_pid()); | 
					
						
							|  |  |  |                     action.set_icon(shared_icon); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 request.menu.icon_buffer_id = action.icon()->shared_buffer_id(); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 request.menu.icon_buffer_id = -1; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-04-26 21:09:56 +02:00
										 |  |  |             if (action.is_checkable()) | 
					
						
							|  |  |  |                 request.menu.checked = action.is_checked(); | 
					
						
							| 
									
										
										
										
											2019-02-25 22:06:55 +01:00
										 |  |  |             ASSERT(action.text().length() < (ssize_t)sizeof(request.text)); | 
					
						
							| 
									
										
										
										
											2019-02-14 01:21:32 +01:00
										 |  |  |             strcpy(request.text, action.text().characters()); | 
					
						
							|  |  |  |             request.text_length = action.text().length(); | 
					
						
							| 
									
										
										
										
											2019-03-02 10:04:49 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if (action.shortcut().is_valid()) { | 
					
						
							|  |  |  |                 auto shortcut_text = action.shortcut().to_string(); | 
					
						
							|  |  |  |                 ASSERT(shortcut_text.length() < (ssize_t)sizeof(request.menu.shortcut_text)); | 
					
						
							|  |  |  |                 strcpy(request.menu.shortcut_text, shortcut_text.characters()); | 
					
						
							|  |  |  |                 request.menu.shortcut_text_length = shortcut_text.length(); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 request.menu.shortcut_text_length = 0; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 20:57:27 +02:00
										 |  |  |             GWindowServerConnection::the().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuItem); | 
					
						
							| 
									
										
										
										
											2019-02-12 14:09:48 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-02-12 00:52:19 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-02-12 10:08:35 +01:00
										 |  |  |     all_menus().set(m_menu_id, this); | 
					
						
							| 
									
										
										
										
											2019-02-12 00:52:19 +01:00
										 |  |  |     return m_menu_id; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-02-12 14:09:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-13 18:48:22 +01:00
										 |  |  | void GMenu::unrealize_menu() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2019-05-26 01:07:30 +02:00
										 |  |  |     if (m_menu_id == -1) | 
					
						
							| 
									
										
										
										
											2019-02-13 18:48:22 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     all_menus().remove(m_menu_id); | 
					
						
							| 
									
										
										
										
											2019-02-15 09:17:18 +01:00
										 |  |  |     WSAPI_ClientMessage request; | 
					
						
							|  |  |  |     request.type = WSAPI_ClientMessage::Type::DestroyMenu; | 
					
						
							| 
									
										
										
										
											2019-02-13 18:48:22 +01:00
										 |  |  |     request.menu.menu_id = m_menu_id; | 
					
						
							| 
									
										
										
										
											2019-07-17 20:57:27 +02:00
										 |  |  |     GWindowServerConnection::the().sync_request(request, WSAPI_ServerMessage::Type::DidDestroyMenu); | 
					
						
							| 
									
										
										
										
											2019-02-13 18:48:22 +01:00
										 |  |  |     m_menu_id = 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-25 22:06:55 +01:00
										 |  |  | GAction* GMenu::action_at(int index) | 
					
						
							| 
									
										
										
										
											2019-02-12 14:09:48 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     if (index >= m_items.size()) | 
					
						
							|  |  |  |         return nullptr; | 
					
						
							| 
									
										
										
										
											2019-07-24 09:12:23 +02:00
										 |  |  |     return m_items[index].action(); | 
					
						
							| 
									
										
										
										
											2019-02-12 14:09:48 +01:00
										 |  |  | } |