Window.cpp 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "Window.h"
  7. #include "AppletManager.h"
  8. #include "ClientConnection.h"
  9. #include "Compositor.h"
  10. #include "Event.h"
  11. #include "EventLoop.h"
  12. #include "Screen.h"
  13. #include "WindowManager.h"
  14. #include <AK/Badge.h>
  15. #include <WindowServer/WindowClientEndpoint.h>
  16. #include <ctype.h>
  17. namespace WindowServer {
  18. const static Gfx::IntSize s_default_normal_minimum_size = { 50, 50 };
  19. static String default_window_icon_path()
  20. {
  21. return "/res/icons/16x16/window.png";
  22. }
  23. static Gfx::Bitmap& default_window_icon()
  24. {
  25. static Gfx::Bitmap* s_icon;
  26. if (!s_icon)
  27. s_icon = Gfx::Bitmap::load_from_file(default_window_icon_path()).leak_ref();
  28. return *s_icon;
  29. }
  30. static Gfx::Bitmap& minimize_icon()
  31. {
  32. static Gfx::Bitmap* s_icon;
  33. if (!s_icon)
  34. s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/downward-triangle.png").leak_ref();
  35. return *s_icon;
  36. }
  37. static Gfx::Bitmap& maximize_icon()
  38. {
  39. static Gfx::Bitmap* s_icon;
  40. if (!s_icon)
  41. s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/upward-triangle.png").leak_ref();
  42. return *s_icon;
  43. }
  44. static Gfx::Bitmap& restore_icon()
  45. {
  46. static Gfx::Bitmap* s_icon;
  47. if (!s_icon)
  48. s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-restore.png").leak_ref();
  49. return *s_icon;
  50. }
  51. static Gfx::Bitmap& close_icon()
  52. {
  53. static Gfx::Bitmap* s_icon;
  54. if (!s_icon)
  55. s_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-close.png").leak_ref();
  56. return *s_icon;
  57. }
  58. Window::Window(Core::Object& parent, WindowType type)
  59. : Core::Object(&parent)
  60. , m_type(type)
  61. , m_icon(default_window_icon())
  62. , m_frame(*this)
  63. {
  64. // Set default minimum size for Normal windows
  65. if (m_type == WindowType::Normal)
  66. m_minimum_size = s_default_normal_minimum_size;
  67. WindowManager::the().add_window(*this);
  68. }
  69. Window::Window(ClientConnection& client, WindowType window_type, int window_id, bool modal, bool minimizable, bool frameless, bool resizable, bool fullscreen, bool accessory, Window* parent_window)
  70. : Core::Object(&client)
  71. , m_client(&client)
  72. , m_type(window_type)
  73. , m_modal(modal)
  74. , m_minimizable(minimizable)
  75. , m_frameless(frameless)
  76. , m_resizable(resizable)
  77. , m_fullscreen(fullscreen)
  78. , m_accessory(accessory)
  79. , m_window_id(window_id)
  80. , m_client_id(client.client_id())
  81. , m_icon(default_window_icon())
  82. , m_frame(*this)
  83. {
  84. // Set default minimum size for Normal windows
  85. if (m_type == WindowType::Normal)
  86. m_minimum_size = s_default_normal_minimum_size;
  87. if (parent_window)
  88. set_parent_window(*parent_window);
  89. WindowManager::the().add_window(*this);
  90. }
  91. Window::~Window()
  92. {
  93. // Detach from client at the start of teardown since we don't want
  94. // to confuse things by trying to send messages to it.
  95. m_client = nullptr;
  96. WindowManager::the().remove_window(*this);
  97. }
  98. void Window::destroy()
  99. {
  100. m_destroyed = true;
  101. set_visible(false);
  102. }
  103. void Window::set_title(const String& title)
  104. {
  105. if (m_title == title)
  106. return;
  107. m_title = title;
  108. frame().invalidate_titlebar();
  109. WindowManager::the().notify_title_changed(*this);
  110. }
  111. void Window::set_rect(const Gfx::IntRect& rect)
  112. {
  113. if (m_rect == rect)
  114. return;
  115. auto old_rect = m_rect;
  116. m_rect = rect;
  117. if (rect.is_empty()) {
  118. m_backing_store = nullptr;
  119. } else if (!m_client && (!m_backing_store || old_rect.size() != rect.size())) {
  120. m_backing_store = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, m_rect.size());
  121. }
  122. invalidate(true, old_rect.size() != rect.size());
  123. m_frame.notify_window_rect_changed(old_rect, rect); // recomputes occlusions
  124. }
  125. void Window::set_rect_without_repaint(const Gfx::IntRect& rect)
  126. {
  127. VERIFY(!rect.is_empty());
  128. if (m_rect == rect)
  129. return;
  130. auto old_rect = m_rect;
  131. m_rect = rect;
  132. if (old_rect.size() == m_rect.size()) {
  133. auto delta = m_rect.location() - old_rect.location();
  134. for (auto& child_window : m_child_windows) {
  135. if (child_window)
  136. child_window->move_by(delta);
  137. }
  138. }
  139. invalidate(true, old_rect.size() != rect.size());
  140. m_frame.notify_window_rect_changed(old_rect, rect); // recomputes occlusions
  141. }
  142. bool Window::apply_minimum_size(Gfx::IntRect& rect)
  143. {
  144. int new_width = max(m_minimum_size.width(), rect.width());
  145. int new_height = max(m_minimum_size.height(), rect.height());
  146. bool did_size_clamp = new_width != rect.width() || new_height != rect.height();
  147. rect.set_width(new_width);
  148. rect.set_height(new_height);
  149. return did_size_clamp;
  150. }
  151. void Window::nudge_into_desktop(bool force_titlebar_visible)
  152. {
  153. Gfx::IntRect arena = WindowManager::the().arena_rect_for_type(type());
  154. auto min_visible = 1;
  155. if (type() == WindowType::Normal)
  156. min_visible = 30;
  157. // Push the frame around such that at least `min_visible` pixels of the *frame* are in the desktop rect.
  158. auto old_frame_rect = frame().rect();
  159. Gfx::IntRect new_frame_rect = {
  160. clamp(old_frame_rect.x(), arena.left() + min_visible - width(), arena.right() - min_visible),
  161. clamp(old_frame_rect.y(), arena.top() + min_visible - height(), arena.bottom() - min_visible),
  162. old_frame_rect.width(),
  163. old_frame_rect.height(),
  164. };
  165. // Make sure that at least half of the titlebar is visible.
  166. auto min_frame_y = arena.top() - (y() - old_frame_rect.y()) / 2;
  167. if (force_titlebar_visible && new_frame_rect.y() < min_frame_y) {
  168. new_frame_rect.set_y(min_frame_y);
  169. }
  170. // Deduce new window rect:
  171. Gfx::IntRect new_window_rect = {
  172. x() + new_frame_rect.x() - old_frame_rect.x(),
  173. y() + new_frame_rect.y() - old_frame_rect.y(),
  174. width(),
  175. height(),
  176. };
  177. set_rect(new_window_rect);
  178. }
  179. void Window::set_minimum_size(const Gfx::IntSize& size)
  180. {
  181. VERIFY(!size.is_empty());
  182. if (m_minimum_size == size)
  183. return;
  184. // Disallow setting minimum zero widths or heights.
  185. if (size.width() == 0 || size.height() == 0)
  186. return;
  187. m_minimum_size = size;
  188. }
  189. void Window::handle_mouse_event(const MouseEvent& event)
  190. {
  191. set_automatic_cursor_tracking_enabled(event.buttons() != 0);
  192. switch (event.type()) {
  193. case Event::MouseMove:
  194. m_client->async_mouse_move(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta(), event.is_drag(), event.mime_types());
  195. break;
  196. case Event::MouseDown:
  197. m_client->async_mouse_down(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta());
  198. break;
  199. case Event::MouseDoubleClick:
  200. m_client->async_mouse_double_click(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta());
  201. break;
  202. case Event::MouseUp:
  203. m_client->async_mouse_up(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta());
  204. break;
  205. case Event::MouseWheel:
  206. m_client->async_mouse_wheel(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta());
  207. break;
  208. default:
  209. VERIFY_NOT_REACHED();
  210. }
  211. }
  212. void Window::update_window_menu_items()
  213. {
  214. if (!m_window_menu)
  215. return;
  216. m_window_menu_minimize_item->set_text(m_minimized ? "&Unminimize" : "Mi&nimize");
  217. m_window_menu_minimize_item->set_enabled(m_minimizable);
  218. m_window_menu_maximize_item->set_text(m_maximized ? "&Restore" : "Ma&ximize");
  219. m_window_menu_maximize_item->set_enabled(m_resizable);
  220. }
  221. void Window::set_minimized(bool minimized)
  222. {
  223. if (m_minimized == minimized)
  224. return;
  225. if (minimized && !m_minimizable)
  226. return;
  227. m_minimized = minimized;
  228. update_window_menu_items();
  229. Compositor::the().invalidate_occlusions();
  230. Compositor::the().invalidate_screen(frame().render_rect());
  231. if (!blocking_modal_window())
  232. start_minimize_animation();
  233. if (!minimized)
  234. request_update({ {}, size() });
  235. WindowManager::the().notify_minimization_state_changed(*this);
  236. }
  237. void Window::set_minimizable(bool minimizable)
  238. {
  239. if (m_minimizable == minimizable)
  240. return;
  241. m_minimizable = minimizable;
  242. update_window_menu_items();
  243. // TODO: Hide/show (or alternatively change enabled state of) window minimize button dynamically depending on value of m_minimizable
  244. }
  245. void Window::set_taskbar_rect(const Gfx::IntRect& rect)
  246. {
  247. m_taskbar_rect = rect;
  248. m_have_taskbar_rect = !m_taskbar_rect.is_empty();
  249. }
  250. void Window::start_minimize_animation()
  251. {
  252. if (!m_have_taskbar_rect) {
  253. // If this is a modal window, it may not have its own taskbar
  254. // button, so there is no rectangle. In that case, walk the
  255. // modal stack until we find a window that may have one
  256. WindowManager::the().for_each_window_in_modal_stack(*this, [&](Window& w, bool) {
  257. if (w.has_taskbar_rect()) {
  258. // We purposely do NOT set m_have_taskbar_rect to true here
  259. // because we want to only copy the rectangle from the
  260. // window that has it, but since this window wouldn't receive
  261. // any updates down the road we want to query it again
  262. // next time we want to start the animation
  263. m_taskbar_rect = w.taskbar_rect();
  264. VERIFY(!m_have_taskbar_rect); // should remain unset!
  265. return IterationDecision::Break;
  266. };
  267. return IterationDecision::Continue;
  268. });
  269. }
  270. m_minimize_animation_step = 0;
  271. }
  272. void Window::set_opacity(float opacity)
  273. {
  274. if (m_opacity == opacity)
  275. return;
  276. bool was_opaque = is_opaque();
  277. m_opacity = opacity;
  278. if (was_opaque != is_opaque())
  279. Compositor::the().invalidate_occlusions();
  280. invalidate(false);
  281. WindowManager::the().notify_opacity_changed(*this);
  282. }
  283. void Window::set_has_alpha_channel(bool value)
  284. {
  285. if (m_has_alpha_channel == value)
  286. return;
  287. m_has_alpha_channel = value;
  288. Compositor::the().invalidate_occlusions();
  289. }
  290. void Window::set_occluded(bool occluded)
  291. {
  292. if (m_occluded == occluded)
  293. return;
  294. m_occluded = occluded;
  295. WindowManager::the().notify_occlusion_state_changed(*this);
  296. }
  297. void Window::set_maximized(bool maximized, Optional<Gfx::IntPoint> fixed_point)
  298. {
  299. if (m_maximized == maximized)
  300. return;
  301. if (maximized && (!is_resizable() || resize_aspect_ratio().has_value()))
  302. return;
  303. m_tiled = WindowTileType::None;
  304. m_maximized = maximized;
  305. update_window_menu_items();
  306. if (maximized) {
  307. m_unmaximized_rect = m_rect;
  308. set_rect(WindowManager::the().maximized_window_rect(*this));
  309. } else {
  310. if (fixed_point.has_value()) {
  311. auto new_rect = Gfx::IntRect(m_rect);
  312. new_rect.set_size_around(m_unmaximized_rect.size(), fixed_point.value());
  313. set_rect(new_rect);
  314. } else {
  315. set_rect(m_unmaximized_rect);
  316. }
  317. }
  318. m_frame.did_set_maximized({}, maximized);
  319. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
  320. set_default_positioned(false);
  321. }
  322. void Window::set_vertically_maximized()
  323. {
  324. if (m_maximized)
  325. return;
  326. if (!is_resizable() || resize_aspect_ratio().has_value())
  327. return;
  328. auto max_rect = WindowManager::the().maximized_window_rect(*this);
  329. auto new_rect = Gfx::IntRect(
  330. Gfx::IntPoint(rect().x(), max_rect.y()),
  331. Gfx::IntSize(rect().width(), max_rect.height()));
  332. set_rect(new_rect);
  333. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(new_rect));
  334. }
  335. void Window::set_resizable(bool resizable)
  336. {
  337. if (m_resizable == resizable)
  338. return;
  339. m_resizable = resizable;
  340. update_window_menu_items();
  341. // TODO: Hide/show (or alternatively change enabled state of) window maximize button dynamically depending on value of is_resizable()
  342. }
  343. void Window::event(Core::Event& event)
  344. {
  345. if (!m_client) {
  346. VERIFY(parent());
  347. event.ignore();
  348. return;
  349. }
  350. if (blocking_modal_window()) {
  351. // We still want to handle the WindowDeactivated event below when a new modal is
  352. // created to notify its parent window, despite it being "blocked by modal window".
  353. if (event.type() != Event::WindowDeactivated)
  354. return;
  355. }
  356. if (static_cast<Event&>(event).is_mouse_event())
  357. return handle_mouse_event(static_cast<const MouseEvent&>(event));
  358. switch (event.type()) {
  359. case Event::WindowEntered:
  360. m_client->async_window_entered(m_window_id);
  361. break;
  362. case Event::WindowLeft:
  363. m_client->async_window_left(m_window_id);
  364. break;
  365. case Event::KeyDown:
  366. handle_keydown_event(static_cast<const KeyEvent&>(event));
  367. break;
  368. case Event::KeyUp:
  369. m_client->async_key_up(m_window_id,
  370. (u32) static_cast<const KeyEvent&>(event).code_point(),
  371. (u32) static_cast<const KeyEvent&>(event).key(),
  372. static_cast<const KeyEvent&>(event).modifiers(),
  373. (u32) static_cast<const KeyEvent&>(event).scancode());
  374. break;
  375. case Event::WindowActivated:
  376. m_client->async_window_activated(m_window_id);
  377. break;
  378. case Event::WindowDeactivated:
  379. m_client->async_window_deactivated(m_window_id);
  380. break;
  381. case Event::WindowInputEntered:
  382. m_client->async_window_input_entered(m_window_id);
  383. break;
  384. case Event::WindowInputLeft:
  385. m_client->async_window_input_left(m_window_id);
  386. break;
  387. case Event::WindowCloseRequest:
  388. m_client->async_window_close_request(m_window_id);
  389. break;
  390. case Event::WindowResized:
  391. m_client->async_window_resized(m_window_id, static_cast<const ResizeEvent&>(event).rect());
  392. break;
  393. default:
  394. break;
  395. }
  396. }
  397. void Window::handle_keydown_event(const KeyEvent& event)
  398. {
  399. if (event.modifiers() == Mod_Alt && event.key() == Key_Space && type() == WindowType::Normal && !is_frameless()) {
  400. auto position = frame().titlebar_rect().bottom_left().translated(frame().rect().location());
  401. popup_window_menu(position, WindowMenuDefaultAction::Close);
  402. return;
  403. }
  404. if (event.modifiers() == Mod_Alt && event.code_point() && menubar()) {
  405. Menu* menu_to_open = nullptr;
  406. menubar()->for_each_menu([&](Menu& menu) {
  407. if (tolower(menu.alt_shortcut_character()) == tolower(event.code_point())) {
  408. menu_to_open = &menu;
  409. return IterationDecision::Break;
  410. }
  411. return IterationDecision::Continue;
  412. });
  413. if (menu_to_open) {
  414. frame().open_menubar_menu(*menu_to_open);
  415. if (!menu_to_open->is_empty())
  416. menu_to_open->set_hovered_index(0);
  417. return;
  418. }
  419. }
  420. m_client->async_key_down(m_window_id, (u32)event.code_point(), (u32)event.key(), event.modifiers(), (u32)event.scancode());
  421. }
  422. void Window::set_global_cursor_tracking_enabled(bool enabled)
  423. {
  424. m_global_cursor_tracking_enabled = enabled;
  425. }
  426. void Window::set_visible(bool b)
  427. {
  428. if (m_visible == b)
  429. return;
  430. m_visible = b;
  431. Compositor::the().invalidate_occlusions();
  432. if (m_visible)
  433. invalidate(true);
  434. else
  435. Compositor::the().invalidate_screen(frame().render_rect());
  436. }
  437. void Window::set_frameless(bool frameless)
  438. {
  439. if (m_frameless == frameless)
  440. return;
  441. auto render_rect_before = frame().render_rect();
  442. m_frameless = frameless;
  443. if (m_visible) {
  444. Compositor::the().invalidate_occlusions();
  445. invalidate(true, true);
  446. Compositor::the().invalidate_screen(frameless ? render_rect_before : frame().render_rect());
  447. }
  448. }
  449. void Window::invalidate(bool invalidate_frame, bool re_render_frame)
  450. {
  451. m_invalidated = true;
  452. m_invalidated_all = true;
  453. if (invalidate_frame && !m_invalidated_frame) {
  454. m_invalidated_frame = true;
  455. }
  456. if (re_render_frame)
  457. frame().set_dirty(true);
  458. m_dirty_rects.clear();
  459. Compositor::the().invalidate_window();
  460. }
  461. void Window::invalidate(const Gfx::IntRect& rect, bool with_frame)
  462. {
  463. if (type() == WindowType::Applet) {
  464. AppletManager::the().invalidate_applet(*this, rect);
  465. return;
  466. }
  467. if (invalidate_no_notify(rect, with_frame))
  468. Compositor::the().invalidate_window();
  469. }
  470. bool Window::invalidate_no_notify(const Gfx::IntRect& rect, bool with_frame)
  471. {
  472. if (rect.is_empty())
  473. return false;
  474. if (m_invalidated_all) {
  475. if (with_frame)
  476. m_invalidated_frame |= true;
  477. return false;
  478. }
  479. auto outer_rect = frame().render_rect();
  480. auto inner_rect = rect;
  481. inner_rect.translate_by(position());
  482. // FIXME: This seems slightly wrong; the inner rect shouldn't intersect the border part of the outer rect.
  483. inner_rect.intersect(outer_rect);
  484. if (inner_rect.is_empty())
  485. return false;
  486. m_invalidated = true;
  487. if (with_frame)
  488. m_invalidated_frame |= true;
  489. m_dirty_rects.add(inner_rect.translated(-outer_rect.location()));
  490. return true;
  491. }
  492. void Window::refresh_client_size()
  493. {
  494. client()->async_window_resized(m_window_id, m_rect);
  495. }
  496. void Window::prepare_dirty_rects()
  497. {
  498. if (m_invalidated_all) {
  499. if (m_invalidated_frame)
  500. m_dirty_rects = frame().render_rect();
  501. else
  502. m_dirty_rects = rect();
  503. } else {
  504. m_dirty_rects.move_by(frame().render_rect().location());
  505. if (m_invalidated_frame) {
  506. if (m_invalidated) {
  507. m_dirty_rects.add(frame().render_rect());
  508. } else {
  509. for (auto& rects : frame().render_rect().shatter(rect()))
  510. m_dirty_rects.add(rects);
  511. }
  512. }
  513. }
  514. }
  515. void Window::clear_dirty_rects()
  516. {
  517. m_invalidated_all = false;
  518. m_invalidated_frame = false;
  519. m_invalidated = false;
  520. m_dirty_rects.clear_with_capacity();
  521. }
  522. bool Window::is_active() const
  523. {
  524. return WindowManager::the().active_window() == this;
  525. }
  526. Window* Window::blocking_modal_window()
  527. {
  528. // A window is blocked if any immediate child, or any child further
  529. // down the chain is modal
  530. for (auto& window : m_child_windows) {
  531. if (window && !window->is_destroyed()) {
  532. if (window->is_modal())
  533. return window;
  534. if (auto* blocking_window = window->blocking_modal_window())
  535. return blocking_window;
  536. }
  537. }
  538. return nullptr;
  539. }
  540. void Window::set_default_icon()
  541. {
  542. m_icon = default_window_icon();
  543. }
  544. void Window::request_update(const Gfx::IntRect& rect, bool ignore_occlusion)
  545. {
  546. if (rect.is_empty())
  547. return;
  548. if (m_pending_paint_rects.is_empty()) {
  549. deferred_invoke([this, ignore_occlusion](auto&) {
  550. client()->post_paint_message(*this, ignore_occlusion);
  551. });
  552. }
  553. m_pending_paint_rects.add(rect);
  554. }
  555. void Window::ensure_window_menu()
  556. {
  557. if (!m_window_menu) {
  558. m_window_menu = Menu::construct(nullptr, -1, "(Window Menu)");
  559. m_window_menu->set_window_menu_of(*this);
  560. auto minimize_item = make<MenuItem>(*m_window_menu, (unsigned)WindowMenuAction::MinimizeOrUnminimize, "");
  561. m_window_menu_minimize_item = minimize_item.ptr();
  562. m_window_menu->add_item(move(minimize_item));
  563. auto maximize_item = make<MenuItem>(*m_window_menu, (unsigned)WindowMenuAction::MaximizeOrRestore, "");
  564. m_window_menu_maximize_item = maximize_item.ptr();
  565. m_window_menu->add_item(move(maximize_item));
  566. m_window_menu->add_item(make<MenuItem>(*m_window_menu, MenuItem::Type::Separator));
  567. auto menubar_visibility_item = make<MenuItem>(*m_window_menu, (unsigned)WindowMenuAction::ToggleMenubarVisibility, "Menu &Bar");
  568. m_window_menu_menubar_visibility_item = menubar_visibility_item.ptr();
  569. menubar_visibility_item->set_checkable(true);
  570. m_window_menu->add_item(move(menubar_visibility_item));
  571. auto close_item = make<MenuItem>(*m_window_menu, (unsigned)WindowMenuAction::Close, "&Close");
  572. m_window_menu_close_item = close_item.ptr();
  573. m_window_menu_close_item->set_icon(&close_icon());
  574. m_window_menu_close_item->set_default(true);
  575. m_window_menu->add_item(move(close_item));
  576. m_window_menu->on_item_activation = [&](auto& item) {
  577. handle_window_menu_action(static_cast<WindowMenuAction>(item.identifier()));
  578. };
  579. update_window_menu_items();
  580. }
  581. }
  582. void Window::handle_window_menu_action(WindowMenuAction action)
  583. {
  584. switch (action) {
  585. case WindowMenuAction::MinimizeOrUnminimize:
  586. WindowManager::the().minimize_windows(*this, !m_minimized);
  587. if (!m_minimized)
  588. WindowManager::the().move_to_front_and_make_active(*this);
  589. break;
  590. case WindowMenuAction::MaximizeOrRestore:
  591. WindowManager::the().maximize_windows(*this, !m_maximized);
  592. WindowManager::the().move_to_front_and_make_active(*this);
  593. break;
  594. case WindowMenuAction::Close:
  595. request_close();
  596. break;
  597. case WindowMenuAction::ToggleMenubarVisibility: {
  598. auto& item = *m_window_menu->item_by_identifier((unsigned)action);
  599. frame().invalidate();
  600. item.set_checked(!item.is_checked());
  601. m_should_show_menubar = item.is_checked();
  602. frame().invalidate();
  603. recalculate_rect();
  604. Compositor::the().invalidate_occlusions();
  605. Compositor::the().invalidate_screen();
  606. break;
  607. }
  608. }
  609. }
  610. void Window::popup_window_menu(const Gfx::IntPoint& position, WindowMenuDefaultAction default_action)
  611. {
  612. ensure_window_menu();
  613. if (default_action == WindowMenuDefaultAction::BasedOnWindowState) {
  614. // When clicked on the task bar, determine the default action
  615. if (!is_active() && !is_minimized())
  616. default_action = WindowMenuDefaultAction::None;
  617. else if (is_minimized())
  618. default_action = WindowMenuDefaultAction::Unminimize;
  619. else
  620. default_action = WindowMenuDefaultAction::Minimize;
  621. }
  622. m_window_menu_minimize_item->set_default(default_action == WindowMenuDefaultAction::Minimize || default_action == WindowMenuDefaultAction::Unminimize);
  623. m_window_menu_minimize_item->set_icon(m_minimized ? nullptr : &minimize_icon());
  624. m_window_menu_maximize_item->set_default(default_action == WindowMenuDefaultAction::Maximize || default_action == WindowMenuDefaultAction::Restore);
  625. m_window_menu_maximize_item->set_icon(m_maximized ? &restore_icon() : &maximize_icon());
  626. m_window_menu_close_item->set_default(default_action == WindowMenuDefaultAction::Close);
  627. m_window_menu_menubar_visibility_item->set_enabled(menubar());
  628. m_window_menu_menubar_visibility_item->set_checked(menubar() && m_should_show_menubar);
  629. m_window_menu->popup(position);
  630. }
  631. void Window::window_menu_activate_default()
  632. {
  633. ensure_window_menu();
  634. m_window_menu->activate_default();
  635. }
  636. void Window::request_close()
  637. {
  638. Event close_request(Event::WindowCloseRequest);
  639. event(close_request);
  640. }
  641. void Window::set_fullscreen(bool fullscreen)
  642. {
  643. if (m_fullscreen == fullscreen)
  644. return;
  645. m_fullscreen = fullscreen;
  646. Gfx::IntRect new_window_rect = m_rect;
  647. if (m_fullscreen) {
  648. m_saved_nonfullscreen_rect = m_rect;
  649. new_window_rect = Screen::the().rect();
  650. } else if (!m_saved_nonfullscreen_rect.is_empty()) {
  651. new_window_rect = m_saved_nonfullscreen_rect;
  652. }
  653. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(new_window_rect));
  654. set_rect(new_window_rect);
  655. }
  656. Gfx::IntRect Window::tiled_rect(WindowTileType tiled) const
  657. {
  658. VERIFY(tiled != WindowTileType::None);
  659. int frame_width = (m_frame.rect().width() - m_rect.width()) / 2;
  660. int titlebar_height = m_frame.titlebar_rect().height();
  661. int menu_height = WindowManager::the().maximized_window_rect(*this).y();
  662. int max_height = WindowManager::the().maximized_window_rect(*this).height();
  663. switch (tiled) {
  664. case WindowTileType::Left:
  665. return Gfx::IntRect(0,
  666. menu_height,
  667. Screen::the().width() / 2 - frame_width,
  668. max_height);
  669. case WindowTileType::Right:
  670. return Gfx::IntRect(Screen::the().width() / 2 + frame_width,
  671. menu_height,
  672. Screen::the().width() / 2 - frame_width,
  673. max_height);
  674. case WindowTileType::Top:
  675. return Gfx::IntRect(0,
  676. menu_height,
  677. Screen::the().width(),
  678. (max_height - titlebar_height) / 2 - frame_width);
  679. case WindowTileType::Bottom:
  680. return Gfx::IntRect(0,
  681. menu_height + (titlebar_height + max_height) / 2 + frame_width,
  682. Screen::the().width(),
  683. (max_height - titlebar_height) / 2 - frame_width);
  684. case WindowTileType::TopLeft:
  685. return Gfx::IntRect(0,
  686. menu_height,
  687. Screen::the().width() / 2 - frame_width,
  688. (max_height - titlebar_height) / 2 - frame_width);
  689. case WindowTileType::TopRight:
  690. return Gfx::IntRect(Screen::the().width() / 2 + frame_width,
  691. menu_height,
  692. Screen::the().width() / 2 - frame_width,
  693. (max_height - titlebar_height) / 2 - frame_width);
  694. case WindowTileType::BottomLeft:
  695. return Gfx::IntRect(0,
  696. menu_height + (titlebar_height + max_height) / 2 + frame_width,
  697. Screen::the().width() / 2 - frame_width,
  698. (max_height - titlebar_height) / 2 - frame_width);
  699. case WindowTileType::BottomRight:
  700. return Gfx::IntRect(Screen::the().width() / 2 + frame_width,
  701. menu_height + (titlebar_height + max_height) / 2 + frame_width,
  702. Screen::the().width() / 2 - frame_width,
  703. (max_height - titlebar_height) / 2 - frame_width);
  704. default:
  705. VERIFY_NOT_REACHED();
  706. }
  707. }
  708. bool Window::set_untiled(Optional<Gfx::IntPoint> fixed_point)
  709. {
  710. if (m_tiled == WindowTileType::None)
  711. return false;
  712. VERIFY(!resize_aspect_ratio().has_value());
  713. m_tiled = WindowTileType::None;
  714. if (fixed_point.has_value()) {
  715. auto new_rect = Gfx::IntRect(m_rect);
  716. new_rect.set_size_around(m_untiled_rect.size(), fixed_point.value());
  717. set_rect(new_rect);
  718. } else {
  719. set_rect(m_untiled_rect);
  720. }
  721. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
  722. return true;
  723. }
  724. void Window::set_tiled(WindowTileType tiled)
  725. {
  726. VERIFY(tiled != WindowTileType::None);
  727. if (m_tiled == tiled)
  728. return;
  729. if (resize_aspect_ratio().has_value())
  730. return;
  731. if (m_tiled == WindowTileType::None)
  732. m_untiled_rect = m_rect;
  733. m_tiled = tiled;
  734. set_rect(tiled_rect(tiled));
  735. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
  736. }
  737. void Window::detach_client(Badge<ClientConnection>)
  738. {
  739. m_client = nullptr;
  740. }
  741. void Window::recalculate_rect()
  742. {
  743. if (!is_resizable())
  744. return;
  745. bool send_event = true;
  746. if (m_tiled != WindowTileType::None) {
  747. set_rect(tiled_rect(m_tiled));
  748. } else if (is_maximized()) {
  749. set_rect(WindowManager::the().maximized_window_rect(*this));
  750. } else if (type() == WindowType::Desktop) {
  751. set_rect(WindowManager::the().desktop_rect());
  752. } else {
  753. send_event = false;
  754. }
  755. if (send_event) {
  756. Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect));
  757. }
  758. }
  759. void Window::add_child_window(Window& child_window)
  760. {
  761. m_child_windows.append(child_window);
  762. }
  763. void Window::add_accessory_window(Window& accessory_window)
  764. {
  765. m_accessory_windows.append(accessory_window);
  766. }
  767. void Window::set_parent_window(Window& parent_window)
  768. {
  769. VERIFY(!m_parent_window);
  770. m_parent_window = parent_window;
  771. if (m_accessory)
  772. parent_window.add_accessory_window(*this);
  773. else
  774. parent_window.add_child_window(*this);
  775. }
  776. bool Window::is_accessory() const
  777. {
  778. if (!m_accessory)
  779. return false;
  780. if (parent_window() != nullptr)
  781. return true;
  782. // If accessory window was unparented, convert to a regular window
  783. const_cast<Window*>(this)->set_accessory(false);
  784. return false;
  785. }
  786. bool Window::is_accessory_of(Window& window) const
  787. {
  788. if (!is_accessory())
  789. return false;
  790. return parent_window() == &window;
  791. }
  792. void Window::modal_unparented()
  793. {
  794. m_modal = false;
  795. WindowManager::the().notify_modal_unparented(*this);
  796. }
  797. bool Window::is_modal() const
  798. {
  799. if (!m_modal)
  800. return false;
  801. if (!m_parent_window) {
  802. const_cast<Window*>(this)->modal_unparented();
  803. return false;
  804. }
  805. return true;
  806. }
  807. void Window::set_progress(Optional<int> progress)
  808. {
  809. if (m_progress == progress)
  810. return;
  811. m_progress = progress;
  812. WindowManager::the().notify_progress_changed(*this);
  813. }
  814. bool Window::is_descendant_of(Window& window) const
  815. {
  816. for (auto* parent = parent_window(); parent; parent = parent->parent_window()) {
  817. if (parent == &window)
  818. return true;
  819. for (auto& accessory : parent->accessory_windows()) {
  820. if (accessory == &window)
  821. return true;
  822. }
  823. }
  824. return false;
  825. }
  826. bool Window::hit_test(const Gfx::IntPoint& point, bool include_frame) const
  827. {
  828. if (!frame().rect().contains(point))
  829. return false;
  830. if (!rect().contains(point)) {
  831. if (include_frame)
  832. return frame().hit_test(point);
  833. return false;
  834. }
  835. if (!m_hit_testing_enabled)
  836. return false;
  837. u8 threshold = alpha_hit_threshold() * 255;
  838. if (threshold == 0 || !m_backing_store || !m_backing_store->has_alpha_channel())
  839. return true;
  840. auto relative_point = point.translated(-rect().location()) * m_backing_store->scale();
  841. u8 alpha = 0xff;
  842. if (m_backing_store->rect().contains(relative_point))
  843. alpha = m_backing_store->get_pixel(relative_point).alpha();
  844. return alpha >= threshold;
  845. }
  846. void Window::set_menubar(Menubar* menubar)
  847. {
  848. if (m_menubar == menubar)
  849. return;
  850. m_menubar = menubar;
  851. if (m_menubar) {
  852. // FIXME: Maybe move this to the theming system?
  853. static constexpr auto menubar_menu_margin = 14;
  854. auto& wm = WindowManager::the();
  855. Gfx::IntPoint next_menu_location { 0, 0 };
  856. auto menubar_rect = Gfx::WindowTheme::current().menubar_rect(Gfx::WindowTheme::WindowType::Normal, rect(), wm.palette(), 1);
  857. m_menubar->for_each_menu([&](Menu& menu) {
  858. int text_width = wm.font().width(Gfx::parse_ampersand_string(menu.name()));
  859. menu.set_rect_in_window_menubar({ next_menu_location.x(), 0, text_width + menubar_menu_margin, menubar_rect.height() });
  860. next_menu_location.translate_by(menu.rect_in_window_menubar().width(), 0);
  861. return IterationDecision::Continue;
  862. });
  863. }
  864. Compositor::the().invalidate_occlusions();
  865. frame().invalidate();
  866. }
  867. void Window::invalidate_menubar()
  868. {
  869. if (!m_should_show_menubar || !menubar())
  870. return;
  871. // FIXME: This invalidates way more than the menubar!
  872. frame().invalidate();
  873. }
  874. void Window::set_modified(bool modified)
  875. {
  876. if (m_modified == modified)
  877. return;
  878. m_modified = modified;
  879. frame().set_button_icons();
  880. frame().invalidate_titlebar();
  881. }
  882. }