Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <LibCore/EventLoop.h>
9#include <LibGUI/Desktop.h>
10#include <LibGUI/Dialog.h>
11#include <LibGUI/Event.h>
12#include <LibGfx/Palette.h>
13
14namespace GUI {
15
16Dialog::Dialog(Window* parent_window, ScreenPosition screen_position)
17 : Window(parent_window)
18 , m_screen_position(screen_position)
19{
20 set_window_mode(WindowMode::Blocking);
21}
22
23Dialog::ExecResult Dialog::exec()
24{
25 VERIFY(!m_event_loop);
26 m_event_loop = make<Core::EventLoop>();
27
28 auto desktop_rect = Desktop::the().rect();
29 auto window_rect = rect();
30
31 auto top_align = [](Gfx::Rect<int>& rect) { rect.set_y(32); };
32 auto bottom_align = [this, desktop_rect](Gfx::Rect<int>& rect) { rect.set_y(desktop_rect.height() - Desktop::the().taskbar_height() - height() - 12); };
33
34 auto left_align = [](Gfx::Rect<int>& rect) { rect.set_x(12); };
35 auto right_align = [this, desktop_rect](Gfx::Rect<int>& rect) { rect.set_x(desktop_rect.width() - width() - 12); };
36
37 switch (m_screen_position) {
38 case ScreenPosition::CenterWithinParent:
39 if (parent() && is<Window>(parent())) {
40 auto& parent_window = *static_cast<Window*>(parent());
41 if (parent_window.is_visible()) {
42 // Check the dialog's position against the Desktop's rect and reposition it to be entirely visible.
43 // If the dialog is larger than the desktop's rect just center it.
44 window_rect.center_within(parent_window.rect());
45 if (window_rect.size().width() < desktop_rect.size().width() && window_rect.size().height() < desktop_rect.size().height()) {
46 auto taskbar_top_y = desktop_rect.bottom() - Desktop::the().taskbar_height();
47 auto palette = GUI::Application::the()->palette();
48 auto border_thickness = palette.window_border_thickness();
49 auto top_border_title_thickness = border_thickness + palette.window_title_height();
50 if (window_rect.top() < top_border_title_thickness) {
51 window_rect.set_y(top_border_title_thickness);
52 }
53 if (window_rect.right() + border_thickness > desktop_rect.right()) {
54 window_rect.translate_by((window_rect.right() + border_thickness - desktop_rect.right()) * -1, 0);
55 }
56 if (window_rect.bottom() + border_thickness > taskbar_top_y) {
57 window_rect.translate_by(0, (window_rect.bottom() + border_thickness - taskbar_top_y) * -1);
58 }
59 if (window_rect.left() - border_thickness < 0) {
60 window_rect.set_x(0 + border_thickness);
61 }
62 }
63 break;
64 }
65 }
66 [[fallthrough]]; // Fall back to `Center` if parent window is invalid or not visible
67 case ScreenPosition::Center:
68 window_rect.center_within(desktop_rect);
69 break;
70 case ScreenPosition::CenterLeft:
71 left_align(window_rect);
72 window_rect.center_vertically_within(desktop_rect);
73 break;
74 case ScreenPosition::CenterRight:
75 right_align(window_rect);
76 window_rect.center_vertically_within(desktop_rect);
77 break;
78 case ScreenPosition::TopLeft:
79 left_align(window_rect);
80 top_align(window_rect);
81 break;
82 case ScreenPosition::TopCenter:
83 window_rect.center_horizontally_within(desktop_rect);
84 top_align(window_rect);
85 break;
86 case ScreenPosition::TopRight:
87 right_align(window_rect);
88 top_align(window_rect);
89 break;
90 case ScreenPosition::BottomLeft:
91 left_align(window_rect);
92 bottom_align(window_rect);
93 break;
94 case ScreenPosition::BottomCenter:
95 window_rect.center_horizontally_within(desktop_rect);
96 bottom_align(window_rect);
97 break;
98 case ScreenPosition::BottomRight:
99 right_align(window_rect);
100 bottom_align(window_rect);
101 break;
102 }
103
104 set_rect(window_rect);
105 show();
106 auto result = m_event_loop->exec();
107 m_event_loop = nullptr;
108 dbgln("{}: Event loop returned with result {}", *this, result);
109 remove_from_parent();
110 return static_cast<ExecResult>(result);
111}
112
113void Dialog::done(ExecResult result)
114{
115 Window::close();
116
117 if (!m_event_loop)
118 return;
119 m_result = result;
120 on_done(m_result);
121
122 dbgln("{}: Quit event loop with result {}", *this, to_underlying(result));
123 m_event_loop->quit(to_underlying(result));
124}
125
126void Dialog::event(Core::Event& event)
127{
128 if (event.type() == Event::KeyDown) {
129 auto& key_event = static_cast<KeyEvent&>(event);
130 if (key_event.key() == KeyCode::Key_Escape) {
131 done(ExecResult::Cancel);
132 event.accept();
133 return;
134 }
135 }
136
137 Window::event(event);
138}
139
140void Dialog::close()
141{
142 done(ExecResult::Cancel);
143}
144
145}