MenuManager.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. /*
  2. * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2020, Shannon Booth <shannon.ml.booth@gmail.com>
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are met:
  8. *
  9. * 1. Redistributions of source code must retain the above copyright notice, this
  10. * list of conditions and the following disclaimer.
  11. *
  12. * 2. Redistributions in binary form must reproduce the above copyright notice,
  13. * this list of conditions and the following disclaimer in the documentation
  14. * and/or other materials provided with the distribution.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  20. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  21. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  22. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  23. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  24. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  25. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. #include <AK/Badge.h>
  28. #include <WindowServer/ClientConnection.h>
  29. #include <WindowServer/MenuManager.h>
  30. #include <WindowServer/Screen.h>
  31. #include <WindowServer/WindowManager.h>
  32. namespace WindowServer {
  33. static MenuManager* s_the;
  34. MenuManager& MenuManager::the()
  35. {
  36. VERIFY(s_the);
  37. return *s_the;
  38. }
  39. MenuManager::MenuManager()
  40. {
  41. s_the = this;
  42. }
  43. MenuManager::~MenuManager()
  44. {
  45. }
  46. bool MenuManager::is_open(const Menu& menu) const
  47. {
  48. for (size_t i = 0; i < m_open_menu_stack.size(); ++i) {
  49. if (&menu == m_open_menu_stack[i].ptr())
  50. return true;
  51. }
  52. return false;
  53. }
  54. void MenuManager::refresh()
  55. {
  56. ClientConnection::for_each_client([&](ClientConnection& client) {
  57. client.for_each_menu([&](Menu& menu) {
  58. menu.redraw();
  59. return IterationDecision::Continue;
  60. });
  61. });
  62. }
  63. void MenuManager::event(Core::Event& event)
  64. {
  65. auto& wm = WindowManager::the();
  66. if (static_cast<Event&>(event).is_mouse_event()) {
  67. handle_mouse_event(static_cast<MouseEvent&>(event));
  68. return;
  69. }
  70. if (static_cast<Event&>(event).is_key_event()) {
  71. auto& key_event = static_cast<const KeyEvent&>(event);
  72. if (key_event.type() == Event::KeyUp && key_event.key() == Key_Escape) {
  73. close_everyone();
  74. return;
  75. }
  76. if (m_current_menu && event.type() == Event::KeyDown
  77. && ((key_event.key() >= Key_A && key_event.key() <= Key_Z)
  78. || (key_event.key() >= Key_0 && key_event.key() <= Key_9))) {
  79. if (auto* shortcut_item_indexes = m_current_menu->items_with_alt_shortcut(key_event.code_point())) {
  80. VERIFY(!shortcut_item_indexes->is_empty());
  81. // FIXME: If there are multiple items with the same Alt shortcut, we should cycle through them
  82. // with each keypress instead of activating immediately.
  83. auto index = shortcut_item_indexes->at(0);
  84. auto& item = m_current_menu->item(index);
  85. m_current_menu->set_hovered_index(index);
  86. if (item.is_submenu())
  87. m_current_menu->descend_into_submenu_at_hovered_item();
  88. else
  89. m_current_menu->open_hovered_item(false);
  90. }
  91. return;
  92. }
  93. if (event.type() == Event::KeyDown) {
  94. if (key_event.key() == Key_Left) {
  95. auto it = m_open_menu_stack.find_if([&](const auto& other) { return m_current_menu == other.ptr(); });
  96. VERIFY(!it.is_end());
  97. // Going "back" a menu should be the previous menu in the stack
  98. if (it.index() > 0)
  99. set_current_menu(m_open_menu_stack.at(it.index() - 1));
  100. else {
  101. if (m_current_menu->hovered_item())
  102. m_current_menu->set_hovered_index(-1);
  103. else {
  104. auto* target_menu = previous_menu(m_current_menu);
  105. if (target_menu) {
  106. target_menu->ensure_menu_window().move_to(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()));
  107. open_menu(*target_menu);
  108. wm.window_with_active_menu()->invalidate_menubar();
  109. }
  110. }
  111. }
  112. close_everyone_not_in_lineage(*m_current_menu);
  113. return;
  114. }
  115. if (key_event.key() == Key_Right) {
  116. auto hovered_item = m_current_menu->hovered_item();
  117. if (hovered_item && hovered_item->is_submenu())
  118. m_current_menu->descend_into_submenu_at_hovered_item();
  119. else if (m_open_menu_stack.size() <= 1 && wm.window_with_active_menu()) {
  120. auto* target_menu = next_menu(m_current_menu);
  121. if (target_menu) {
  122. target_menu->ensure_menu_window().move_to(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()));
  123. open_menu(*target_menu);
  124. wm.window_with_active_menu()->invalidate_menubar();
  125. close_everyone_not_in_lineage(*target_menu);
  126. }
  127. }
  128. return;
  129. }
  130. if (key_event.key() == Key_Return) {
  131. auto hovered_item = m_current_menu->hovered_item();
  132. if (!hovered_item || !hovered_item->is_enabled())
  133. return;
  134. if (hovered_item->is_submenu())
  135. m_current_menu->descend_into_submenu_at_hovered_item();
  136. else
  137. m_current_menu->open_hovered_item(key_event.modifiers() & KeyModifier::Mod_Ctrl);
  138. return;
  139. }
  140. m_current_menu->dispatch_event(event);
  141. }
  142. }
  143. return Core::Object::event(event);
  144. }
  145. void MenuManager::handle_mouse_event(MouseEvent& mouse_event)
  146. {
  147. if (!has_open_menu())
  148. return;
  149. auto* topmost_menu = m_open_menu_stack.last().ptr();
  150. VERIFY(topmost_menu);
  151. auto* window = topmost_menu->menu_window();
  152. if (!window) {
  153. dbgln("MenuManager::handle_mouse_event: No menu window");
  154. return;
  155. }
  156. VERIFY(window->is_visible());
  157. bool event_is_inside_current_menu = window->rect().contains(mouse_event.position());
  158. if (event_is_inside_current_menu) {
  159. WindowManager::the().set_hovered_window(window);
  160. auto translated_event = mouse_event.translated(-window->position());
  161. WindowManager::the().deliver_mouse_event(*window, translated_event, true);
  162. return;
  163. }
  164. if (topmost_menu->hovered_item())
  165. topmost_menu->clear_hovered_item();
  166. if (mouse_event.type() == Event::MouseDown || mouse_event.type() == Event::MouseUp) {
  167. auto* window_menu_of = topmost_menu->window_menu_of();
  168. if (window_menu_of) {
  169. bool event_is_inside_taskbar_button = window_menu_of->taskbar_rect().contains(mouse_event.position());
  170. if (event_is_inside_taskbar_button && !topmost_menu->is_window_menu_open()) {
  171. topmost_menu->set_window_menu_open(true);
  172. return;
  173. }
  174. }
  175. if (mouse_event.type() == Event::MouseDown) {
  176. for (auto& menu : m_open_menu_stack) {
  177. if (!menu)
  178. continue;
  179. if (!menu->menu_window()->rect().contains(mouse_event.position()))
  180. continue;
  181. return;
  182. }
  183. MenuManager::the().close_everyone();
  184. topmost_menu->set_window_menu_open(false);
  185. }
  186. }
  187. if (mouse_event.type() == Event::MouseMove) {
  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. WindowManager::the().set_hovered_window(menu->menu_window());
  194. auto translated_event = mouse_event.translated(-menu->menu_window()->position());
  195. WindowManager::the().deliver_mouse_event(*menu->menu_window(), translated_event, true);
  196. break;
  197. }
  198. }
  199. }
  200. void MenuManager::close_all_menus_from_client(Badge<ClientConnection>, ClientConnection& client)
  201. {
  202. if (!has_open_menu())
  203. return;
  204. if (m_open_menu_stack.first()->client() != &client)
  205. return;
  206. close_everyone();
  207. }
  208. void MenuManager::close_everyone()
  209. {
  210. for (auto& menu : m_open_menu_stack) {
  211. VERIFY(menu);
  212. menu->set_visible(false);
  213. menu->clear_hovered_item();
  214. }
  215. m_open_menu_stack.clear();
  216. clear_current_menu();
  217. refresh();
  218. }
  219. void MenuManager::close_everyone_not_in_lineage(Menu& menu)
  220. {
  221. Vector<Menu*> menus_to_close;
  222. for (auto& open_menu : m_open_menu_stack) {
  223. if (!open_menu)
  224. continue;
  225. if (&menu == open_menu.ptr() || open_menu->is_menu_ancestor_of(menu))
  226. continue;
  227. menus_to_close.append(open_menu);
  228. }
  229. close_menus(menus_to_close);
  230. }
  231. void MenuManager::close_menus(const Vector<Menu*>& menus)
  232. {
  233. for (auto& menu : menus) {
  234. if (menu == m_current_menu)
  235. clear_current_menu();
  236. menu->set_visible(false);
  237. menu->clear_hovered_item();
  238. m_open_menu_stack.remove_first_matching([&](auto& entry) {
  239. return entry == menu;
  240. });
  241. }
  242. refresh();
  243. }
  244. static void collect_menu_subtree(Menu& menu, Vector<Menu*>& menus)
  245. {
  246. menus.append(&menu);
  247. for (int i = 0; i < menu.item_count(); ++i) {
  248. auto& item = menu.item(i);
  249. if (!item.is_submenu())
  250. continue;
  251. collect_menu_subtree(*item.submenu(), menus);
  252. }
  253. }
  254. void MenuManager::close_menu_and_descendants(Menu& menu)
  255. {
  256. Vector<Menu*> menus_to_close;
  257. collect_menu_subtree(menu, menus_to_close);
  258. close_menus(menus_to_close);
  259. }
  260. void MenuManager::set_hovered_menu(Menu* menu)
  261. {
  262. if (m_hovered_menu == menu)
  263. return;
  264. if (menu) {
  265. m_hovered_menu = menu->make_weak_ptr<Menu>();
  266. } else {
  267. // FIXME: This is quite aggressive. If we knew which window the previously hovered menu was in,
  268. // we could just invalidate that one instead of iterating all windows in the client.
  269. if (auto* client = m_hovered_menu->client()) {
  270. client->for_each_window([&](Window& window) {
  271. window.invalidate_menubar();
  272. return IterationDecision::Continue;
  273. });
  274. }
  275. m_hovered_menu = nullptr;
  276. }
  277. }
  278. void MenuManager::open_menu(Menu& menu, bool as_current_menu)
  279. {
  280. if (is_open(menu)) {
  281. if (as_current_menu || current_menu() != &menu) {
  282. // This menu is already open. If requested, or if the current
  283. // window doesn't match this one, then set it to this
  284. set_current_menu(&menu);
  285. }
  286. return;
  287. }
  288. if (!menu.is_empty()) {
  289. menu.redraw_if_theme_changed();
  290. if (!menu.menu_window())
  291. menu.ensure_menu_window();
  292. menu.set_visible(true);
  293. }
  294. if (m_open_menu_stack.find_if([&menu](auto& other) { return &menu == other.ptr(); }).is_end())
  295. m_open_menu_stack.append(menu);
  296. if (as_current_menu || !current_menu()) {
  297. // Only make this menu the current menu if requested, or if no
  298. // other menu is current
  299. set_current_menu(&menu);
  300. }
  301. refresh();
  302. }
  303. void MenuManager::clear_current_menu()
  304. {
  305. Menu* previous_current_menu = m_current_menu;
  306. m_current_menu = nullptr;
  307. if (previous_current_menu) {
  308. // When closing the last menu, restore the previous active input window
  309. auto& wm = WindowManager::the();
  310. wm.restore_active_input_window(m_previous_input_window);
  311. if (auto* window = wm.window_with_active_menu()) {
  312. window->invalidate_menubar();
  313. }
  314. wm.set_window_with_active_menu(nullptr);
  315. }
  316. }
  317. void MenuManager::set_current_menu(Menu* menu)
  318. {
  319. if (!menu) {
  320. clear_current_menu();
  321. return;
  322. }
  323. VERIFY(is_open(*menu));
  324. if (menu == m_current_menu) {
  325. return;
  326. }
  327. Menu* previous_current_menu = m_current_menu;
  328. m_current_menu = menu;
  329. auto& wm = WindowManager::the();
  330. if (!previous_current_menu) {
  331. // When opening the first menu, store the current active input window
  332. if (auto* active_input = wm.active_input_window())
  333. m_previous_input_window = *active_input;
  334. else
  335. m_previous_input_window = nullptr;
  336. }
  337. wm.set_active_input_window(m_current_menu->menu_window());
  338. }
  339. Menu* MenuManager::previous_menu(Menu* current)
  340. {
  341. auto& wm = WindowManager::the();
  342. if (!wm.window_with_active_menu())
  343. return nullptr;
  344. Menu* found = nullptr;
  345. Menu* previous = nullptr;
  346. wm.window_with_active_menu()->menubar()->for_each_menu([&](Menu& menu) {
  347. if (current == &menu) {
  348. found = previous;
  349. return IterationDecision::Break;
  350. }
  351. previous = &menu;
  352. return IterationDecision::Continue;
  353. });
  354. return found;
  355. }
  356. Menu* MenuManager::next_menu(Menu* current)
  357. {
  358. Menu* found = nullptr;
  359. bool is_next = false;
  360. auto& wm = WindowManager::the();
  361. if (!wm.window_with_active_menu())
  362. return nullptr;
  363. wm.window_with_active_menu()->menubar()->for_each_menu([&](Menu& menu) {
  364. if (is_next) {
  365. found = &menu;
  366. return IterationDecision::Break;
  367. }
  368. if (current == &menu)
  369. is_next = true;
  370. return IterationDecision::Continue;
  371. });
  372. return found;
  373. }
  374. void MenuManager::did_change_theme()
  375. {
  376. ++m_theme_index;
  377. refresh();
  378. }
  379. }