MenuManager.cpp 13 KB

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