Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/HashMap.h>
28#include <AK/JsonObject.h>
29#include <AK/NeverDestroyed.h>
30#include <AK/SharedBuffer.h>
31#include <LibCore/EventLoop.h>
32#include <LibCore/MimeData.h>
33#include <LibGUI/Action.h>
34#include <LibGUI/Application.h>
35#include <LibGUI/Event.h>
36#include <LibGUI/Painter.h>
37#include <LibGUI/Widget.h>
38#include <LibGUI/Window.h>
39#include <LibGUI/WindowServerConnection.h>
40#include <LibGfx/Bitmap.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <unistd.h>
44
45//#define UPDATE_COALESCING_DEBUG
46
47namespace GUI {
48
49static NeverDestroyed<HashTable<Window*>> all_windows;
50static NeverDestroyed<HashMap<int, Window*>> reified_windows;
51
52Window* Window::from_window_id(int window_id)
53{
54 auto it = reified_windows->find(window_id);
55 if (it != reified_windows->end())
56 return (*it).value;
57 return nullptr;
58}
59
60Window::Window(Core::Object* parent)
61 : Core::Object(parent)
62{
63 all_windows->set(this);
64 m_rect_when_windowless = { 100, 400, 140, 140 };
65 m_title_when_windowless = "GWindow";
66}
67
68Window::~Window()
69{
70 all_windows->remove(this);
71 hide();
72}
73
74void Window::close()
75{
76 hide();
77}
78
79void Window::move_to_front()
80{
81 if (!m_window_id)
82 return;
83
84 WindowServerConnection::the().send_sync<Messages::WindowServer::MoveWindowToFront>(m_window_id);
85}
86
87void Window::show()
88{
89 if (m_window_id)
90 return;
91 auto response = WindowServerConnection::the().send_sync<Messages::WindowServer::CreateWindow>(
92 m_rect_when_windowless,
93 m_has_alpha_channel,
94 m_modal,
95 m_minimizable,
96 m_resizable,
97 m_fullscreen,
98 m_show_titlebar,
99 m_opacity_when_windowless,
100 m_base_size,
101 m_size_increment,
102 (i32)m_window_type,
103 m_title_when_windowless);
104 m_window_id = response->window_id();
105
106 apply_icon();
107
108 reified_windows->set(m_window_id, this);
109 Application::the().did_create_window({});
110 update();
111}
112
113void Window::hide()
114{
115 if (!m_window_id)
116 return;
117 reified_windows->remove(m_window_id);
118 WindowServerConnection::the().send_sync<Messages::WindowServer::DestroyWindow>(m_window_id);
119 m_window_id = 0;
120 m_pending_paint_event_rects.clear();
121 m_back_bitmap = nullptr;
122 m_front_bitmap = nullptr;
123
124 bool app_has_visible_windows = false;
125 for (auto& window : *all_windows) {
126 if (window->is_visible()) {
127 app_has_visible_windows = true;
128 break;
129 }
130 }
131 if (!app_has_visible_windows)
132 Application::the().did_delete_last_window({});
133}
134
135void Window::set_title(const StringView& title)
136{
137 m_title_when_windowless = title;
138 if (!m_window_id)
139 return;
140 WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowTitle>(m_window_id, title);
141}
142
143String Window::title() const
144{
145 if (!m_window_id)
146 return m_title_when_windowless;
147 return WindowServerConnection::the().send_sync<Messages::WindowServer::GetWindowTitle>(m_window_id)->title();
148}
149
150Gfx::Rect Window::rect() const
151{
152 if (!m_window_id)
153 return m_rect_when_windowless;
154 return WindowServerConnection::the().send_sync<Messages::WindowServer::GetWindowRect>(m_window_id)->rect();
155}
156
157void Window::set_rect(const Gfx::Rect& a_rect)
158{
159 m_rect_when_windowless = a_rect;
160 if (!m_window_id) {
161 if (m_main_widget)
162 m_main_widget->resize(m_rect_when_windowless.size());
163 return;
164 }
165 WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowRect>(m_window_id, a_rect);
166 if (m_back_bitmap && m_back_bitmap->size() != a_rect.size())
167 m_back_bitmap = nullptr;
168 if (m_front_bitmap && m_front_bitmap->size() != a_rect.size())
169 m_front_bitmap = nullptr;
170 if (m_main_widget)
171 m_main_widget->resize(a_rect.size());
172}
173
174void Window::set_window_type(WindowType window_type)
175{
176 m_window_type = window_type;
177}
178
179void Window::set_override_cursor(StandardCursor cursor)
180{
181 if (!m_window_id)
182 return;
183 WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowOverrideCursor>(m_window_id, (u32)cursor);
184}
185
186void Window::event(Core::Event& event)
187{
188 if (event.type() == Event::Drop) {
189 auto& drop_event = static_cast<DropEvent&>(event);
190 if (!m_main_widget)
191 return;
192 auto result = m_main_widget->hit_test(drop_event.position());
193 auto local_event = make<DropEvent>(result.local_position, drop_event.text(), drop_event.mime_data());
194 ASSERT(result.widget);
195 return result.widget->dispatch_event(*local_event, this);
196 }
197
198 if (event.type() == Event::MouseUp || event.type() == Event::MouseDown || event.type() == Event::MouseDoubleClick || event.type() == Event::MouseMove || event.type() == Event::MouseWheel) {
199 auto& mouse_event = static_cast<MouseEvent&>(event);
200 if (m_global_cursor_tracking_widget) {
201 auto window_relative_rect = m_global_cursor_tracking_widget->window_relative_rect();
202 Gfx::Point local_point { mouse_event.x() - window_relative_rect.x(), mouse_event.y() - window_relative_rect.y() };
203 auto local_event = make<MouseEvent>((Event::Type)event.type(), local_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers(), mouse_event.wheel_delta());
204 m_global_cursor_tracking_widget->dispatch_event(*local_event, this);
205 return;
206 }
207 if (m_automatic_cursor_tracking_widget) {
208 auto window_relative_rect = m_automatic_cursor_tracking_widget->window_relative_rect();
209 Gfx::Point local_point { mouse_event.x() - window_relative_rect.x(), mouse_event.y() - window_relative_rect.y() };
210 auto local_event = make<MouseEvent>((Event::Type)event.type(), local_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers(), mouse_event.wheel_delta());
211 m_automatic_cursor_tracking_widget->dispatch_event(*local_event, this);
212 if (mouse_event.buttons() == 0)
213 m_automatic_cursor_tracking_widget = nullptr;
214 return;
215 }
216 if (!m_main_widget)
217 return;
218 auto result = m_main_widget->hit_test(mouse_event.position());
219 auto local_event = make<MouseEvent>((Event::Type)event.type(), result.local_position, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers(), mouse_event.wheel_delta());
220 ASSERT(result.widget);
221 set_hovered_widget(result.widget);
222 if (mouse_event.buttons() != 0 && !m_automatic_cursor_tracking_widget)
223 m_automatic_cursor_tracking_widget = result.widget->make_weak_ptr();
224 if (result.widget != m_global_cursor_tracking_widget.ptr())
225 return result.widget->dispatch_event(*local_event, this);
226 return;
227 }
228
229 if (event.type() == Event::MultiPaint) {
230 if (!m_window_id)
231 return;
232 if (!m_main_widget)
233 return;
234 auto& paint_event = static_cast<MultiPaintEvent&>(event);
235 auto rects = paint_event.rects();
236 ASSERT(!rects.is_empty());
237 if (m_back_bitmap && m_back_bitmap->size() != paint_event.window_size()) {
238 // Eagerly discard the backing store if we learn from this paint event that it needs to be bigger.
239 // Otherwise we would have to wait for a resize event to tell us. This way we don't waste the
240 // effort on painting into an undersized bitmap that will be thrown away anyway.
241 m_back_bitmap = nullptr;
242 }
243 bool created_new_backing_store = !m_back_bitmap;
244 if (!m_back_bitmap) {
245 m_back_bitmap = create_backing_bitmap(paint_event.window_size());
246 } else if (m_double_buffering_enabled) {
247 bool still_has_pixels = m_back_bitmap->shared_buffer()->set_nonvolatile();
248 if (!still_has_pixels) {
249 m_back_bitmap = create_backing_bitmap(paint_event.window_size());
250 created_new_backing_store = true;
251 }
252 }
253
254 auto rect = rects.first();
255 if (rect.is_empty() || created_new_backing_store) {
256 rects.clear();
257 rects.append({ {}, paint_event.window_size() });
258 }
259
260 for (auto& rect : rects)
261 m_main_widget->dispatch_event(*make<PaintEvent>(rect), this);
262
263 if (m_double_buffering_enabled)
264 flip(rects);
265 else if (created_new_backing_store)
266 set_current_backing_bitmap(*m_back_bitmap, true);
267
268 if (m_window_id) {
269 Vector<Gfx::Rect> rects_to_send;
270 for (auto& r : rects)
271 rects_to_send.append(r);
272 WindowServerConnection::the().post_message(Messages::WindowServer::DidFinishPainting(m_window_id, rects_to_send));
273 }
274 return;
275 }
276
277 if (event.type() == Event::KeyUp || event.type() == Event::KeyDown) {
278 if (m_focused_widget)
279 return m_focused_widget->dispatch_event(event, this);
280 if (m_main_widget)
281 return m_main_widget->dispatch_event(event, this);
282 return;
283 }
284
285 if (event.type() == Event::WindowBecameActive || event.type() == Event::WindowBecameInactive) {
286 m_is_active = event.type() == Event::WindowBecameActive;
287 if (m_main_widget)
288 m_main_widget->dispatch_event(event, this);
289 if (m_focused_widget)
290 m_focused_widget->update();
291 return;
292 }
293
294 if (event.type() == Event::WindowCloseRequest) {
295 if (on_close_request) {
296 if (on_close_request() == Window::CloseRequestDecision::StayOpen)
297 return;
298 }
299 close();
300 return;
301 }
302
303 if (event.type() == Event::WindowLeft) {
304 set_hovered_widget(nullptr);
305 return;
306 }
307
308 if (event.type() == Event::Resize) {
309 auto new_size = static_cast<ResizeEvent&>(event).size();
310 if (m_back_bitmap && m_back_bitmap->size() != new_size)
311 m_back_bitmap = nullptr;
312 if (!m_pending_paint_event_rects.is_empty()) {
313 m_pending_paint_event_rects.clear_with_capacity();
314 m_pending_paint_event_rects.append({ {}, new_size });
315 }
316 m_rect_when_windowless = { {}, new_size };
317 m_main_widget->set_relative_rect({ {}, new_size });
318 return;
319 }
320
321 if (event.type() > Event::__Begin_WM_Events && event.type() < Event::__End_WM_Events)
322 return wm_event(static_cast<WMEvent&>(event));
323
324 if (event.type() == Event::DragMove) {
325 if (!m_main_widget)
326 return;
327 auto& drag_event = static_cast<DragEvent&>(event);
328 auto result = m_main_widget->hit_test(drag_event.position());
329 auto local_event = make<DragEvent>(static_cast<Event::Type>(drag_event.type()), result.local_position, drag_event.data_type());
330 ASSERT(result.widget);
331 return result.widget->dispatch_event(*local_event, this);
332 }
333
334 Core::Object::event(event);
335}
336
337bool Window::is_visible() const
338{
339 return m_window_id != 0;
340}
341
342void Window::update()
343{
344 auto rect = this->rect();
345 update({ 0, 0, rect.width(), rect.height() });
346}
347
348void Window::force_update()
349{
350 auto rect = this->rect();
351 WindowServerConnection::the().post_message(Messages::WindowServer::InvalidateRect(m_window_id, { { 0, 0, rect.width(), rect.height() } }, true));
352}
353
354void Window::update(const Gfx::Rect& a_rect)
355{
356 if (!m_window_id)
357 return;
358
359 for (auto& pending_rect : m_pending_paint_event_rects) {
360 if (pending_rect.contains(a_rect)) {
361#ifdef UPDATE_COALESCING_DEBUG
362 dbgprintf("Ignoring %s since it's contained by pending rect %s\n", a_rect.to_string().characters(), pending_rect.to_string().characters());
363#endif
364 return;
365 }
366 }
367
368 if (m_pending_paint_event_rects.is_empty()) {
369 deferred_invoke([this](auto&) {
370 auto rects = move(m_pending_paint_event_rects);
371 if (rects.is_empty())
372 return;
373 Vector<Gfx::Rect> rects_to_send;
374 for (auto& r : rects)
375 rects_to_send.append(r);
376 WindowServerConnection::the().post_message(Messages::WindowServer::InvalidateRect(m_window_id, rects_to_send, false));
377 });
378 }
379 m_pending_paint_event_rects.append(a_rect);
380}
381
382void Window::set_main_widget(Widget* widget)
383{
384 if (m_main_widget == widget)
385 return;
386 if (m_main_widget)
387 remove_child(*m_main_widget);
388 m_main_widget = widget;
389 if (m_main_widget) {
390 add_child(*widget);
391 auto new_window_rect = rect();
392 if (m_main_widget->horizontal_size_policy() == SizePolicy::Fixed)
393 new_window_rect.set_width(m_main_widget->preferred_size().width());
394 if (m_main_widget->vertical_size_policy() == SizePolicy::Fixed)
395 new_window_rect.set_height(m_main_widget->preferred_size().height());
396 set_rect(new_window_rect);
397 m_main_widget->set_relative_rect({ {}, new_window_rect.size() });
398 m_main_widget->set_window(this);
399 if (m_main_widget->accepts_focus())
400 m_main_widget->set_focus(true);
401 }
402 update();
403}
404
405void Window::set_focused_widget(Widget* widget)
406{
407 if (m_focused_widget == widget)
408 return;
409 if (m_focused_widget) {
410 Core::EventLoop::current().post_event(*m_focused_widget, make<Event>(Event::FocusOut));
411 m_focused_widget->update();
412 }
413 m_focused_widget = widget ? widget->make_weak_ptr() : nullptr;
414 if (m_focused_widget) {
415 Core::EventLoop::current().post_event(*m_focused_widget, make<Event>(Event::FocusIn));
416 m_focused_widget->update();
417 }
418}
419
420void Window::set_global_cursor_tracking_widget(Widget* widget)
421{
422 if (widget == m_global_cursor_tracking_widget)
423 return;
424 m_global_cursor_tracking_widget = widget ? widget->make_weak_ptr() : nullptr;
425}
426
427void Window::set_automatic_cursor_tracking_widget(Widget* widget)
428{
429 if (widget == m_automatic_cursor_tracking_widget)
430 return;
431 m_automatic_cursor_tracking_widget = widget ? widget->make_weak_ptr() : nullptr;
432}
433
434void Window::set_has_alpha_channel(bool value)
435{
436 if (m_has_alpha_channel == value)
437 return;
438 m_has_alpha_channel = value;
439 if (!m_window_id)
440 return;
441
442 m_pending_paint_event_rects.clear();
443 m_back_bitmap = nullptr;
444 m_front_bitmap = nullptr;
445
446 WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowHasAlphaChannel>(m_window_id, value);
447 update();
448}
449
450void Window::set_double_buffering_enabled(bool value)
451{
452 ASSERT(!m_window_id);
453 m_double_buffering_enabled = value;
454}
455
456void Window::set_opacity(float opacity)
457{
458 m_opacity_when_windowless = opacity;
459 if (!m_window_id)
460 return;
461 WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowOpacity>(m_window_id, opacity);
462}
463
464void Window::set_hovered_widget(Widget* widget)
465{
466 if (widget == m_hovered_widget)
467 return;
468
469 if (m_hovered_widget)
470 Core::EventLoop::current().post_event(*m_hovered_widget, make<Event>(Event::Leave));
471
472 m_hovered_widget = widget ? widget->make_weak_ptr() : nullptr;
473
474 if (m_hovered_widget)
475 Core::EventLoop::current().post_event(*m_hovered_widget, make<Event>(Event::Enter));
476}
477
478void Window::set_current_backing_bitmap(Gfx::Bitmap& bitmap, bool flush_immediately)
479{
480 WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowBackingStore>(m_window_id, 32, bitmap.pitch(), bitmap.shared_buffer_id(), bitmap.has_alpha_channel(), bitmap.size(), flush_immediately);
481}
482
483void Window::flip(const Vector<Gfx::Rect, 32>& dirty_rects)
484{
485 swap(m_front_bitmap, m_back_bitmap);
486
487 set_current_backing_bitmap(*m_front_bitmap);
488
489 if (!m_back_bitmap || m_back_bitmap->size() != m_front_bitmap->size()) {
490 m_back_bitmap = create_backing_bitmap(m_front_bitmap->size());
491 memcpy(m_back_bitmap->scanline(0), m_front_bitmap->scanline(0), m_front_bitmap->size_in_bytes());
492 m_back_bitmap->shared_buffer()->set_volatile();
493 return;
494 }
495
496 // Copy whatever was painted from the front to the back.
497 Painter painter(*m_back_bitmap);
498 for (auto& dirty_rect : dirty_rects)
499 painter.blit(dirty_rect.location(), *m_front_bitmap, dirty_rect);
500
501 m_back_bitmap->shared_buffer()->set_volatile();
502}
503
504NonnullRefPtr<Gfx::Bitmap> Window::create_shared_bitmap(Gfx::BitmapFormat format, const Gfx::Size& size)
505{
506 ASSERT(WindowServerConnection::the().server_pid());
507 ASSERT(!size.is_empty());
508 size_t pitch = round_up_to_power_of_two(size.width() * sizeof(Gfx::RGBA32), 16);
509 size_t size_in_bytes = size.height() * pitch;
510 auto shared_buffer = SharedBuffer::create_with_size(size_in_bytes);
511 ASSERT(shared_buffer);
512 shared_buffer->share_with(WindowServerConnection::the().server_pid());
513 return Gfx::Bitmap::create_with_shared_buffer(format, *shared_buffer, size);
514}
515
516NonnullRefPtr<Gfx::Bitmap> Window::create_backing_bitmap(const Gfx::Size& size)
517{
518 auto format = m_has_alpha_channel ? Gfx::BitmapFormat::RGBA32 : Gfx::BitmapFormat::RGB32;
519 return create_shared_bitmap(format, size);
520}
521
522void Window::set_modal(bool modal)
523{
524 ASSERT(!m_window_id);
525 m_modal = modal;
526}
527
528void Window::wm_event(WMEvent&)
529{
530}
531
532void Window::set_icon(const Gfx::Bitmap* icon)
533{
534 if (m_icon == icon)
535 return;
536
537 m_icon = create_shared_bitmap(Gfx::BitmapFormat::RGBA32, icon->size());
538 {
539 Painter painter(*m_icon);
540 painter.blit({ 0, 0 }, *icon, icon->rect());
541 }
542
543 apply_icon();
544}
545
546void Window::apply_icon()
547{
548 if (!m_icon)
549 return;
550
551 if (!m_window_id)
552 return;
553
554 int rc = seal_shared_buffer(m_icon->shared_buffer_id());
555 ASSERT(rc == 0);
556
557 rc = share_buffer_globally(m_icon->shared_buffer_id());
558 ASSERT(rc == 0);
559
560 static bool has_set_process_icon;
561 if (!has_set_process_icon)
562 set_process_icon(m_icon->shared_buffer_id());
563
564 WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowIconBitmap>(m_window_id, m_icon->shared_buffer_id(), m_icon->size());
565}
566
567void Window::start_wm_resize()
568{
569 WindowServerConnection::the().post_message(Messages::WindowServer::WM_StartWindowResize(WindowServerConnection::the().my_client_id(), m_window_id));
570}
571
572Vector<Widget*> Window::focusable_widgets() const
573{
574 if (!m_main_widget)
575 return {};
576
577 Vector<Widget*> collected_widgets;
578
579 Function<void(Widget&)> collect_focusable_widgets = [&](auto& widget) {
580 if (widget.accepts_focus())
581 collected_widgets.append(&widget);
582 widget.for_each_child_widget([&](auto& child) {
583 if (!child.is_visible())
584 return IterationDecision::Continue;
585 if (!child.is_enabled())
586 return IterationDecision::Continue;
587 collect_focusable_widgets(child);
588 return IterationDecision::Continue;
589 });
590 };
591
592 collect_focusable_widgets(const_cast<Widget&>(*m_main_widget));
593 return collected_widgets;
594}
595
596void Window::save_to(AK::JsonObject& json)
597{
598 json.set("title", title());
599 json.set("visible", is_visible());
600 json.set("active", is_active());
601 json.set("minimizable", is_minimizable());
602 json.set("resizable", is_resizable());
603 json.set("fullscreen", is_fullscreen());
604 json.set("rect", rect().to_string());
605 json.set("base_size", base_size().to_string());
606 json.set("size_increment", size_increment().to_string());
607 Core::Object::save_to(json);
608}
609
610void Window::set_fullscreen(bool fullscreen)
611{
612 if (m_fullscreen == fullscreen)
613 return;
614 m_fullscreen = fullscreen;
615 if (!m_window_id)
616 return;
617 WindowServerConnection::the().send_sync<Messages::WindowServer::SetFullscreen>(m_window_id, fullscreen);
618}
619
620void Window::schedule_relayout()
621{
622 if (m_layout_pending)
623 return;
624 m_layout_pending = true;
625 deferred_invoke([this](auto&) {
626 if (main_widget())
627 main_widget()->do_layout();
628 update();
629 m_layout_pending = false;
630 });
631}
632
633void Window::update_all_windows(Badge<WindowServerConnection>)
634{
635 for (auto* window : *all_windows) {
636 window->force_update();
637 }
638}
639
640void Window::notify_state_changed(Badge<WindowServerConnection>, bool minimized, bool occluded)
641{
642 m_visible_for_timer_purposes = !minimized && !occluded;
643
644 // When double buffering is enabled, minimization/occlusion means we can mark the front bitmap volatile (in addition to the back bitmap.)
645 // When double buffering is disabled, there is only the back bitmap (which we can now mark volatile!)
646 RefPtr<Gfx::Bitmap>& bitmap = m_double_buffering_enabled ? m_front_bitmap : m_back_bitmap;
647 if (!bitmap)
648 return;
649 if (minimized || occluded) {
650 bitmap->shared_buffer()->set_volatile();
651 } else {
652 if (!bitmap->shared_buffer()->set_nonvolatile()) {
653 bitmap = nullptr;
654 update();
655 }
656 }
657}
658
659Action* Window::action_for_key_event(const KeyEvent& event)
660{
661 Shortcut shortcut(event.modifiers(), (KeyCode)event.key());
662 Action* found_action = nullptr;
663 for_each_child_of_type<Action>([&](auto& action) {
664 if (action.shortcut() == shortcut) {
665 found_action = &action;
666 return IterationDecision::Break;
667 }
668 return IterationDecision::Continue;
669 });
670 return found_action;
671}
672
673void Window::set_base_size(const Gfx::Size& base_size)
674{
675 if (m_base_size == base_size)
676 return;
677 m_base_size = base_size;
678 if (m_window_id)
679 WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowBaseSizeAndSizeIncrement>(m_window_id, m_base_size, m_size_increment);
680}
681
682void Window::set_size_increment(const Gfx::Size& size_increment)
683{
684 if (m_size_increment == size_increment)
685 return;
686 m_size_increment = size_increment;
687 if (m_window_id)
688 WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowBaseSizeAndSizeIncrement>(m_window_id, m_base_size, m_size_increment);
689}
690
691}