MenuManager.cpp 13 KB

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