Serenity Operating System
at master 403 lines 13 kB view raw
1/* 2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/Badge.h> 9#include <WindowServer/ConnectionFromClient.h> 10#include <WindowServer/MenuManager.h> 11#include <WindowServer/Screen.h> 12#include <WindowServer/WindowManager.h> 13 14namespace WindowServer { 15 16static MenuManager* s_the; 17 18MenuManager& MenuManager::the() 19{ 20 VERIFY(s_the); 21 return *s_the; 22} 23 24MenuManager::MenuManager() 25{ 26 s_the = this; 27} 28 29bool MenuManager::is_open(Menu const& menu) const 30{ 31 for (size_t i = 0; i < m_open_menu_stack.size(); ++i) { 32 if (&menu == m_open_menu_stack[i].ptr()) 33 return true; 34 } 35 return false; 36} 37 38void MenuManager::refresh() 39{ 40 ConnectionFromClient::for_each_client([&](ConnectionFromClient& client) { 41 client.for_each_menu([&](Menu& menu) { 42 menu.redraw(); 43 return IterationDecision::Continue; 44 }); 45 }); 46} 47 48void MenuManager::event(Core::Event& event) 49{ 50 auto& wm = WindowManager::the(); 51 52 if (static_cast<Event&>(event).is_mouse_event()) { 53 handle_mouse_event(static_cast<MouseEvent&>(event)); 54 return; 55 } 56 57 if (static_cast<Event&>(event).is_key_event()) { 58 auto& key_event = static_cast<KeyEvent const&>(event); 59 60 if (key_event.type() == Event::KeyUp && key_event.key() == Key_Escape) { 61 close_everyone(); 62 return; 63 } 64 65 if (m_current_menu && event.type() == Event::KeyDown 66 && ((key_event.key() >= Key_A && key_event.key() <= Key_Z) 67 || (key_event.key() >= Key_0 && key_event.key() <= Key_9))) { 68 69 if (auto* shortcut_item_indices = m_current_menu->items_with_alt_shortcut(key_event.code_point())) { 70 VERIFY(!shortcut_item_indices->is_empty()); 71 auto it = shortcut_item_indices->find_if([&](int const& i) { return i > m_current_menu->hovered_item_index(); }); 72 auto index = shortcut_item_indices->at(it.is_end() ? 0 : it.index()); 73 auto& item = m_current_menu->item(index); 74 m_current_menu->set_hovered_index(index); 75 if (shortcut_item_indices->size() > 1) 76 return; 77 if (item.is_submenu()) 78 m_current_menu->descend_into_submenu_at_hovered_item(); 79 else 80 m_current_menu->open_hovered_item(false); 81 } 82 83 return; 84 } 85 86 if (event.type() == Event::KeyDown) { 87 88 if (key_event.key() == Key_Left) { 89 auto it = m_open_menu_stack.find_if([&](auto const& other) { return m_current_menu == other.ptr(); }); 90 VERIFY(!it.is_end()); 91 92 // Going "back" a menu should be the previous menu in the stack 93 if (it.index() > 0) 94 set_current_menu(m_open_menu_stack.at(it.index() - 1)); 95 else { 96 if (m_current_menu->hovered_item()) 97 m_current_menu->set_hovered_index(-1); 98 else { 99 auto* target_menu = previous_menu(m_current_menu); 100 if (target_menu) { 101 target_menu->ensure_menu_window(target_menu->rect_in_window_menubar().bottom_left().translated(wm.window_with_active_menu()->frame().rect().location()).translated(wm.window_with_active_menu()->frame().menubar_rect().location())); 102 open_menu(*target_menu); 103 wm.window_with_active_menu()->invalidate_menubar(); 104 } 105 } 106 } 107 close_everyone_not_in_lineage(*m_current_menu); 108 return; 109 } 110 111 if (key_event.key() == Key_Right) { 112 auto hovered_item = m_current_menu->hovered_item(); 113 if (hovered_item && hovered_item->is_submenu()) 114 m_current_menu->descend_into_submenu_at_hovered_item(); 115 else if (m_open_menu_stack.size() <= 1 && wm.window_with_active_menu()) { 116 auto* target_menu = next_menu(m_current_menu); 117 if (target_menu) { 118 target_menu->ensure_menu_window(target_menu->rect_in_window_menubar().bottom_left().translated(wm.window_with_active_menu()->frame().rect().location()).translated(wm.window_with_active_menu()->frame().menubar_rect().location())); 119 open_menu(*target_menu); 120 wm.window_with_active_menu()->invalidate_menubar(); 121 close_everyone_not_in_lineage(*target_menu); 122 } 123 } 124 return; 125 } 126 127 if (key_event.key() == Key_Return) { 128 auto hovered_item = m_current_menu->hovered_item(); 129 if (!hovered_item || !hovered_item->is_enabled()) 130 return; 131 if (hovered_item->is_submenu()) 132 m_current_menu->descend_into_submenu_at_hovered_item(); 133 else 134 m_current_menu->open_hovered_item(key_event.modifiers() & KeyModifier::Mod_Ctrl); 135 return; 136 } 137 138 if (key_event.key() == Key_Space) { 139 auto* hovered_item = m_current_menu->hovered_item(); 140 if (!hovered_item || !hovered_item->is_enabled()) 141 return; 142 if (!hovered_item->is_checkable()) 143 return; 144 145 m_current_menu->open_hovered_item(true); 146 } 147 148 m_current_menu->dispatch_event(event); 149 } 150 } 151 152 return Core::Object::event(event); 153} 154 155void MenuManager::handle_mouse_event(MouseEvent& mouse_event) 156{ 157 if (!has_open_menu()) 158 return; 159 auto* topmost_menu = m_open_menu_stack.last().ptr(); 160 VERIFY(topmost_menu); 161 auto* window = topmost_menu->menu_window(); 162 if (!window) { 163 dbgln("MenuManager::handle_mouse_event: No menu window"); 164 return; 165 } 166 VERIFY(window->is_visible()); 167 168 bool event_is_inside_current_menu = window->rect().contains(mouse_event.position()); 169 if (event_is_inside_current_menu) { 170 WindowManager::the().set_hovered_window(window); 171 WindowManager::the().deliver_mouse_event(*window, mouse_event); 172 return; 173 } 174 175 if (topmost_menu->hovered_item()) 176 topmost_menu->clear_hovered_item(); 177 if (mouse_event.type() == Event::MouseDown || mouse_event.type() == Event::MouseUp) { 178 auto* window_menu_of = topmost_menu->window_menu_of(); 179 if (window_menu_of) { 180 bool event_is_inside_taskbar_button = window_menu_of->taskbar_rect().contains(mouse_event.position()); 181 if (event_is_inside_taskbar_button && !topmost_menu->is_window_menu_open()) { 182 topmost_menu->set_window_menu_open(true); 183 return; 184 } 185 } 186 187 if (mouse_event.type() == Event::MouseDown) { 188 for (auto& menu : m_open_menu_stack) { 189 if (!menu) 190 continue; 191 if (!menu->menu_window()->rect().contains(mouse_event.position())) 192 continue; 193 return; 194 } 195 MenuManager::the().close_everyone(); 196 topmost_menu->set_window_menu_open(false); 197 } 198 } 199 200 if (mouse_event.type() == Event::MouseMove) { 201 for (auto& menu : m_open_menu_stack.in_reverse()) { 202 if (!menu) 203 continue; 204 if (!menu->menu_window()->rect().contains(mouse_event.position())) 205 continue; 206 WindowManager::the().set_hovered_window(menu->menu_window()); 207 WindowManager::the().deliver_mouse_event(*menu->menu_window(), mouse_event); 208 break; 209 } 210 } 211} 212 213void MenuManager::close_all_menus_from_client(Badge<ConnectionFromClient>, ConnectionFromClient& client) 214{ 215 if (!has_open_menu()) 216 return; 217 if (m_open_menu_stack.first()->client() != &client) 218 return; 219 close_everyone(); 220} 221 222void MenuManager::close_everyone() 223{ 224 for (auto& menu : m_open_menu_stack) { 225 VERIFY(menu); 226 menu->set_visible(false); 227 menu->clear_hovered_item(); 228 } 229 m_open_menu_stack.clear(); 230 clear_current_menu(); 231} 232 233Menu* MenuManager::closest_open_ancestor_of(Menu const& other) const 234{ 235 for (auto& menu : m_open_menu_stack.in_reverse()) 236 if (menu->is_menu_ancestor_of(other)) 237 return menu.ptr(); 238 return nullptr; 239} 240 241void MenuManager::close_everyone_not_in_lineage(Menu& menu) 242{ 243 Vector<Menu&> menus_to_close; 244 for (auto& open_menu : m_open_menu_stack) { 245 if (!open_menu) 246 continue; 247 if (&menu == open_menu.ptr() || open_menu->is_menu_ancestor_of(menu)) 248 continue; 249 menus_to_close.append(*open_menu); 250 } 251 close_menus(menus_to_close); 252} 253 254void MenuManager::close_menus(Vector<Menu&>& menus) 255{ 256 for (auto& menu : menus) { 257 if (&menu == m_current_menu) 258 clear_current_menu(); 259 menu.set_visible(false); 260 menu.clear_hovered_item(); 261 m_open_menu_stack.remove_first_matching([&](auto& entry) { 262 return entry == &menu; 263 }); 264 } 265} 266 267static void collect_menu_subtree(Menu& menu, Vector<Menu&>& menus) 268{ 269 menus.append(menu); 270 for (size_t i = 0; i < menu.item_count(); ++i) { 271 auto& item = menu.item(i); 272 if (!item.is_submenu()) 273 continue; 274 collect_menu_subtree(*item.submenu(), menus); 275 } 276} 277 278void MenuManager::close_menu_and_descendants(Menu& menu) 279{ 280 Vector<Menu&> menus_to_close; 281 collect_menu_subtree(menu, menus_to_close); 282 close_menus(menus_to_close); 283} 284 285void MenuManager::set_hovered_menu(Menu* menu) 286{ 287 if (m_hovered_menu == menu) 288 return; 289 if (menu) { 290 m_hovered_menu = menu->make_weak_ptr<Menu>(); 291 } else { 292 // FIXME: This is quite aggressive. If we knew which window the previously hovered menu was in, 293 // we could just invalidate that one instead of iterating all windows in the client. 294 if (auto* client = m_hovered_menu->client()) { 295 client->for_each_window([&](Window& window) { 296 window.invalidate_menubar(); 297 return IterationDecision::Continue; 298 }); 299 } 300 m_hovered_menu = nullptr; 301 } 302} 303 304void MenuManager::open_menu(Menu& menu, bool as_current_menu) 305{ 306 if (menu.is_open()) { 307 if (as_current_menu || current_menu() != &menu) { 308 // This menu is already open. If requested, or if the current 309 // window doesn't match this one, then set it to this 310 set_current_menu(&menu); 311 } 312 return; 313 } 314 315 m_open_menu_stack.append(menu); 316 317 menu.set_visible(true); 318 319 if (!menu.is_empty()) { 320 menu.redraw_if_theme_changed(); 321 auto* window = menu.menu_window(); 322 VERIFY(window); 323 window->set_visible(true); 324 } 325 326 if (as_current_menu || !current_menu()) { 327 // Only make this menu the current menu if requested, or if no 328 // other menu is current 329 set_current_menu(&menu); 330 } 331} 332 333void MenuManager::clear_current_menu() 334{ 335 if (m_current_menu) { 336 auto& wm = WindowManager::the(); 337 if (auto* window = wm.window_with_active_menu()) { 338 window->invalidate_menubar(); 339 } 340 wm.set_window_with_active_menu(nullptr); 341 } 342 m_current_menu = nullptr; 343} 344 345void MenuManager::set_current_menu(Menu* menu) 346{ 347 if (!menu) { 348 clear_current_menu(); 349 return; 350 } 351 352 VERIFY(is_open(*menu)); 353 if (menu == m_current_menu) { 354 return; 355 } 356 357 m_current_menu = menu; 358} 359 360Menu* MenuManager::previous_menu(Menu* current) 361{ 362 auto& wm = WindowManager::the(); 363 if (!wm.window_with_active_menu()) 364 return nullptr; 365 Menu* found = nullptr; 366 Menu* previous = nullptr; 367 wm.window_with_active_menu()->menubar().for_each_menu([&](Menu& menu) { 368 if (current == &menu) { 369 found = previous; 370 return IterationDecision::Break; 371 } 372 previous = &menu; 373 return IterationDecision::Continue; 374 }); 375 return found; 376} 377 378Menu* MenuManager::next_menu(Menu* current) 379{ 380 Menu* found = nullptr; 381 bool is_next = false; 382 auto& wm = WindowManager::the(); 383 if (!wm.window_with_active_menu()) 384 return nullptr; 385 wm.window_with_active_menu()->menubar().for_each_menu([&](Menu& menu) { 386 if (is_next) { 387 found = &menu; 388 return IterationDecision::Break; 389 } 390 if (current == &menu) 391 is_next = true; 392 return IterationDecision::Continue; 393 }); 394 return found; 395} 396 397void MenuManager::did_change_theme() 398{ 399 ++m_theme_index; 400 refresh(); 401} 402 403}