From 57ff293a51d97742d50987950c86dfcde55e6ad1 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 19 Mar 2019 00:01:02 +0100 Subject: [PATCH] LibGUI: Implement nested event loops to support dialog boxes. This patch adds a simple GMessageBox that can run in a nested event loop. Here's how you use it: GMessageBox box("Message text here", "Message window title"); int result = box.exec(); The next step is to make the WindowServer respect the modality flag of these windows and prevent interaction with other windows in the same process until the modal window has been closed. --- Applications/About/main.cpp | 2 +- Applications/FileManager/main.cpp | 2 +- Applications/FontEditor/main.cpp | 2 +- Applications/IRCClient/IRCAppWindow.cpp | 4 ++ Applications/Launcher/main.cpp | 2 +- Applications/ProcessManager/main.cpp | 2 +- Applications/Terminal/main.cpp | 2 +- Applications/TextEditor/main.cpp | 2 +- Kernel/makeall.sh | 4 +- LibGUI/GBoxLayout.cpp | 3 +- LibGUI/GButton.cpp | 4 +- LibGUI/GButton.h | 2 +- LibGUI/GClipboard.cpp | 6 +- LibGUI/GDialog.cpp | 26 +++++++++ LibGUI/GDialog.h | 19 +++++++ LibGUI/GEventLoop.cpp | 75 ++++++++++++++++++++----- LibGUI/GEventLoop.h | 22 +++++--- LibGUI/GMenu.cpp | 8 +-- LibGUI/GMenuBar.cpp | 8 +-- LibGUI/GMessageBox.cpp | 45 +++++++++++++++ LibGUI/GMessageBox.h | 16 ++++++ LibGUI/GNotifier.cpp | 4 +- LibGUI/GObject.cpp | 23 ++++++-- LibGUI/GObject.h | 2 + LibGUI/GWindow.cpp | 51 +++++++++-------- LibGUI/GWindow.h | 10 +++- LibGUI/Makefile | 2 + Userland/guitest2.cpp | 2 +- WindowServer/WSAPITypes.h | 4 ++ 29 files changed, 275 insertions(+), 79 deletions(-) create mode 100644 LibGUI/GDialog.cpp create mode 100644 LibGUI/GDialog.h create mode 100644 LibGUI/GMessageBox.cpp create mode 100644 LibGUI/GMessageBox.h diff --git a/Applications/About/main.cpp b/Applications/About/main.cpp index b98b4ca496a..045b02681d5 100644 --- a/Applications/About/main.cpp +++ b/Applications/About/main.cpp @@ -14,7 +14,7 @@ int main(int argc, char** argv) Rect window_rect { 0, 0, 240, 120 }; window_rect.center_within({ 0, 0, 1024, 768 }); window->set_rect(window_rect); - window->set_should_exit_app_on_close(true); + window->set_should_exit_event_loop_on_close(true); auto* widget = new GWidget; window->set_main_widget(widget); diff --git a/Applications/FileManager/main.cpp b/Applications/FileManager/main.cpp index a3a9eafed37..d7b4ab5c9db 100644 --- a/Applications/FileManager/main.cpp +++ b/Applications/FileManager/main.cpp @@ -29,7 +29,7 @@ int main(int argc, char** argv) auto* window = new GWindow; window->set_title("FileManager"); window->set_rect(20, 200, 640, 480); - window->set_should_exit_app_on_close(true); + window->set_should_exit_event_loop_on_close(true); auto* widget = new GWidget; widget->set_layout(make(Orientation::Vertical)); diff --git a/Applications/FontEditor/main.cpp b/Applications/FontEditor/main.cpp index bf0511e6da4..36f5e1d4029 100644 --- a/Applications/FontEditor/main.cpp +++ b/Applications/FontEditor/main.cpp @@ -29,7 +29,7 @@ int main(int argc, char** argv) window->set_rect({ 50, 50, 420, 300 }); auto* font_editor = new FontEditorWidget(path, move(edited_font)); window->set_main_widget(font_editor); - window->set_should_exit_app_on_close(true); + window->set_should_exit_event_loop_on_close(true); window->show(); return app.exec(); } diff --git a/Applications/IRCClient/IRCAppWindow.cpp b/Applications/IRCClient/IRCAppWindow.cpp index d61e56fcf34..1b431ecbd60 100644 --- a/Applications/IRCClient/IRCAppWindow.cpp +++ b/Applications/IRCClient/IRCAppWindow.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include IRCAppWindow::IRCAppWindow() @@ -40,6 +41,9 @@ void IRCAppWindow::setup_client() }; m_client.on_connect = [this] { + GMessageBox box("We are connected!", "Message"); + int code = box.exec(); + dbgprintf("GMessageBox::exec() returned %d\n", code); m_client.join_channel("#test"); }; diff --git a/Applications/Launcher/main.cpp b/Applications/Launcher/main.cpp index 44aca4d460c..616df66b0bf 100644 --- a/Applications/Launcher/main.cpp +++ b/Applications/Launcher/main.cpp @@ -27,7 +27,7 @@ int main(int argc, char** argv) signal(SIGCHLD, handle_sigchld); auto* launcher_window = make_launcher_window(); - launcher_window->set_should_exit_app_on_close(true); + launcher_window->set_should_exit_event_loop_on_close(true); launcher_window->show(); return app.exec(); diff --git a/Applications/ProcessManager/main.cpp b/Applications/ProcessManager/main.cpp index 14008e63b70..38bfdde050a 100644 --- a/Applications/ProcessManager/main.cpp +++ b/Applications/ProcessManager/main.cpp @@ -71,7 +71,7 @@ int main(int argc, char** argv) window->set_title("ProcessManager"); window->set_rect(20, 200, 640, 400); window->set_main_widget(widget); - window->set_should_exit_app_on_close(true); + window->set_should_exit_event_loop_on_close(true); window->show(); return app.exec(); diff --git a/Applications/Terminal/main.cpp b/Applications/Terminal/main.cpp index 808b14e9b3a..a3cba5d2964 100644 --- a/Applications/Terminal/main.cpp +++ b/Applications/Terminal/main.cpp @@ -89,7 +89,7 @@ int main(int argc, char** argv) auto* window = new GWindow; window->set_double_buffering_enabled(false); - window->set_should_exit_app_on_close(true); + window->set_should_exit_event_loop_on_close(true); Terminal terminal(ptm_fd); window->set_has_alpha_channel(true); diff --git a/Applications/TextEditor/main.cpp b/Applications/TextEditor/main.cpp index 4df70732412..16023de1247 100644 --- a/Applications/TextEditor/main.cpp +++ b/Applications/TextEditor/main.cpp @@ -136,7 +136,7 @@ int main(int argc, char** argv) window->set_title(String::format("TextEditor: %s", path.characters())); window->set_rect(20, 200, 640, 400); window->set_main_widget(widget); - window->set_should_exit_app_on_close(true); + window->set_should_exit_event_loop_on_close(true); text_editor->set_focus(true); window->show(); diff --git a/Kernel/makeall.sh b/Kernel/makeall.sh index 2929983813e..d246871f3f4 100755 --- a/Kernel/makeall.sh +++ b/Kernel/makeall.sh @@ -12,12 +12,12 @@ $make_cmd -C ../LibM && \ (cd ../LibM && ./install.sh) && \ $make_cmd -C ../LibM clean && \ $make_cmd -C ../LibM clean && \ +$make_cmd -C ../WindowServer clean && \ +$make_cmd -C ../WindowServer && \ $make_cmd -C ../LibGUI clean && \ $make_cmd -C ../LibGUI && \ $make_cmd -C ../Userland clean && \ $make_cmd -C ../Userland && \ -$make_cmd -C ../WindowServer clean && \ -$make_cmd -C ../WindowServer && \ $make_cmd -C ../Applications/Terminal clean && \ $make_cmd -C ../Applications/Terminal && \ $make_cmd -C ../Applications/FontEditor clean && \ diff --git a/LibGUI/GBoxLayout.cpp b/LibGUI/GBoxLayout.cpp index 9abea5ed622..8e5ecad8755 100644 --- a/LibGUI/GBoxLayout.cpp +++ b/LibGUI/GBoxLayout.cpp @@ -37,7 +37,8 @@ void GBoxLayout::run(GWidget& widget) should_log = true; #endif if (should_log) - printf("GBoxLayout: running layout on %s{%p}\n", widget.class_name(), &widget); + printf("GBoxLayout: running layout on %s{%p}, entry count: %d\n", widget.class_name(), &widget, m_entries.size()); + if (m_entries.is_empty()) return; diff --git a/LibGUI/GButton.cpp b/LibGUI/GButton.cpp index 4b1a35b9888..9023aaac19b 100644 --- a/LibGUI/GButton.cpp +++ b/LibGUI/GButton.cpp @@ -13,11 +13,11 @@ GButton::~GButton() { } -void GButton::set_caption(String&& caption) +void GButton::set_caption(const String& caption) { if (caption == m_caption) return; - m_caption = move(caption); + m_caption = caption; update(); } diff --git a/LibGUI/GButton.h b/LibGUI/GButton.h index d5897a5d7a6..3e76476d15f 100644 --- a/LibGUI/GButton.h +++ b/LibGUI/GButton.h @@ -12,7 +12,7 @@ public: virtual ~GButton() override; String caption() const { return m_caption; } - void set_caption(String&&); + void set_caption(const String&); void set_icon(RetainPtr&& icon) { m_icon = move(icon); } const GraphicsBitmap* icon() const { return m_icon.ptr(); } diff --git a/LibGUI/GClipboard.cpp b/LibGUI/GClipboard.cpp index d6fe9efba33..9a349a157df 100644 --- a/LibGUI/GClipboard.cpp +++ b/LibGUI/GClipboard.cpp @@ -19,7 +19,7 @@ String GClipboard::data() const { WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::GetClipboardContents; - auto response = GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidGetClipboardContents); + auto response = GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidGetClipboardContents); if (response.clipboard.shared_buffer_id < 0) return { }; auto shared_buffer = SharedBuffer::create_from_shared_buffer_id(response.clipboard.shared_buffer_id); @@ -38,7 +38,7 @@ void GClipboard::set_data(const String& data) { WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::SetClipboardContents; - auto shared_buffer = SharedBuffer::create(GEventLoop::main().server_pid(), data.length() + 1); + auto shared_buffer = SharedBuffer::create(GEventLoop::current().server_pid(), data.length() + 1); if (!shared_buffer) { dbgprintf("GClipboard::set_data() failed to create a shared buffer\n"); return; @@ -50,6 +50,6 @@ void GClipboard::set_data(const String& data) shared_buffer->seal(); request.clipboard.shared_buffer_id = shared_buffer->shared_buffer_id(); request.clipboard.contents_size = data.length(); - auto response = GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidSetClipboardContents); + auto response = GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidSetClipboardContents); ASSERT(response.clipboard.shared_buffer_id == shared_buffer->shared_buffer_id()); } diff --git a/LibGUI/GDialog.cpp b/LibGUI/GDialog.cpp new file mode 100644 index 00000000000..97f0c103c60 --- /dev/null +++ b/LibGUI/GDialog.cpp @@ -0,0 +1,26 @@ +#include +#include + +GDialog::GDialog(GObject* parent) + : GWindow(parent) +{ + set_modal(true); + set_should_exit_event_loop_on_close(true); +} + +GDialog::~GDialog() +{ +} + +int GDialog::exec() +{ + GEventLoop loop; + show(); + return loop.exec(); +} + +void GDialog::done(int result) +{ + m_result = result; + GEventLoop::current().quit(result); +} diff --git a/LibGUI/GDialog.h b/LibGUI/GDialog.h new file mode 100644 index 00000000000..9dcf88a772e --- /dev/null +++ b/LibGUI/GDialog.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class GDialog : public GWindow { +public: + virtual ~GDialog() override; + + int exec(); + + int result() const { return m_result; } + void done(int result); + +protected: + explicit GDialog(GObject* parent); + +private: + int m_result { 0 }; +}; diff --git a/LibGUI/GEventLoop.cpp b/LibGUI/GEventLoop.cpp index 2015a65ae7a..ed9aba0726c 100644 --- a/LibGUI/GEventLoop.cpp +++ b/LibGUI/GEventLoop.cpp @@ -21,11 +21,16 @@ static HashMap* g_actions; static GEventLoop* s_main_event_loop; +static Vector* s_event_loop_stack; int GEventLoop::s_event_fd = -1; pid_t GEventLoop::s_server_pid = -1; +HashMap>* GEventLoop::s_timers; +HashTable* GEventLoop::s_notifiers; +int GEventLoop::s_next_timer_id = 1; void GEventLoop::connect_to_server() { + ASSERT(s_event_fd == -1); s_event_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); if (s_event_fd < 0) { perror("socket"); @@ -51,12 +56,25 @@ void GEventLoop::connect_to_server() if (rc < 0) { ASSERT_NOT_REACHED(); } + + WSAPI_ClientMessage request; + request.type = WSAPI_ClientMessage::Type::Greeting; + request.greeting.client_pid = getpid(); + auto response = sync_request(request, WSAPI_ServerMessage::Type::Greeting); + s_server_pid = response.greeting.server_pid; } GEventLoop::GEventLoop() { + if (!s_event_loop_stack) { + s_event_loop_stack = new Vector; + s_timers = new HashMap>; + s_notifiers = new HashTable; + } + if (!s_main_event_loop) { s_main_event_loop = this; + s_event_loop_stack->append(this); connect_to_server(); } @@ -78,14 +96,41 @@ GEventLoop& GEventLoop::main() return *s_main_event_loop; } +GEventLoop& GEventLoop::current() +{ + return *s_event_loop_stack->last(); +} + void GEventLoop::quit(int code) { m_exit_requested = true; m_exit_code = code; } +struct GEventLoopPusher { +public: + GEventLoopPusher(GEventLoop& event_loop) : m_event_loop(event_loop) + { + if (&m_event_loop != s_main_event_loop) { + m_event_loop.take_pending_events_from(GEventLoop::current()); + s_event_loop_stack->append(&event_loop); + } + } + ~GEventLoopPusher() + { + if (&m_event_loop != s_main_event_loop) { + s_event_loop_stack->take_last(); + GEventLoop::current().take_pending_events_from(m_event_loop); + } + } +private: + GEventLoop& m_event_loop; +}; + int GEventLoop::exec() { + GEventLoopPusher pusher(*this); + m_running = true; for (;;) { if (m_exit_requested) @@ -228,7 +273,7 @@ void GEventLoop::wait_for_event() }; add_fd_to_set(s_event_fd, rfds); - for (auto& notifier : m_notifiers) { + for (auto& notifier : *s_notifiers) { if (notifier->event_mask() & GNotifier::Read) add_fd_to_set(notifier->fd(), rfds); if (notifier->event_mask() & GNotifier::Write) @@ -238,15 +283,15 @@ void GEventLoop::wait_for_event() } struct timeval timeout = { 0, 0 }; - if (!m_timers.is_empty() && m_queued_events.is_empty()) + if (!s_timers->is_empty() && m_queued_events.is_empty()) get_next_timer_expiration(timeout); ASSERT(m_unprocessed_messages.is_empty()); - int rc = select(max_fd + 1, &rfds, &wfds, nullptr, (m_queued_events.is_empty() && m_timers.is_empty()) ? nullptr : &timeout); + int rc = select(max_fd + 1, &rfds, &wfds, nullptr, (m_queued_events.is_empty() && s_timers->is_empty()) ? nullptr : &timeout); if (rc < 0) { ASSERT_NOT_REACHED(); } - for (auto& it : m_timers) { + for (auto& it : *s_timers) { auto& timer = *it.value; if (!timer.has_expired()) continue; @@ -262,7 +307,7 @@ void GEventLoop::wait_for_event() } } - for (auto& notifier : m_notifiers) { + for (auto& notifier : *s_notifiers) { if (FD_ISSET(notifier->fd(), &rfds)) { if (notifier->on_ready_to_read) notifier->on_ready_to_read(*notifier); @@ -378,9 +423,9 @@ void GEventLoop::EventLoopTimer::reload() void GEventLoop::get_next_timer_expiration(timeval& soonest) { - ASSERT(!m_timers.is_empty()); + ASSERT(!s_timers->is_empty()); bool has_checked_any = false; - for (auto& it : m_timers) { + for (auto& it : *s_timers) { auto& fire_time = it.value->fire_time; if (!has_checked_any || fire_time.tv_sec < soonest.tv_sec || (fire_time.tv_sec == soonest.tv_sec && fire_time.tv_usec < soonest.tv_usec)) soonest = fire_time; @@ -396,30 +441,30 @@ int GEventLoop::register_timer(GObject& object, int milliseconds, bool should_re timer->interval = milliseconds; timer->reload(); timer->should_reload = should_reload; - int timer_id = ++m_next_timer_id; // FIXME: This will eventually wrap around. + int timer_id = ++s_next_timer_id; // FIXME: This will eventually wrap around. ASSERT(timer_id); // FIXME: Aforementioned wraparound. timer->timer_id = timer_id; - m_timers.set(timer->timer_id, move(timer)); + s_timers->set(timer->timer_id, move(timer)); return timer_id; } bool GEventLoop::unregister_timer(int timer_id) { - auto it = m_timers.find(timer_id); - if (it == m_timers.end()) + auto it = s_timers->find(timer_id); + if (it == s_timers->end()) return false; - m_timers.remove(it); + s_timers->remove(it); return true; } void GEventLoop::register_notifier(Badge, GNotifier& notifier) { - m_notifiers.set(¬ifier); + s_notifiers->set(¬ifier); } void GEventLoop::unregister_notifier(Badge, GNotifier& notifier) { - m_notifiers.remove(¬ifier); + s_notifiers->remove(¬ifier); } bool GEventLoop::post_message_to_server(const WSAPI_ClientMessage& message) @@ -456,7 +501,7 @@ WSAPI_ServerMessage GEventLoop::sync_request(const WSAPI_ClientMessage& request, ASSERT(success); WSAPI_ServerMessage response; - success = GEventLoop::main().wait_for_specific_event(response_type, response); + success = wait_for_specific_event(response_type, response); ASSERT(success); return response; } diff --git a/LibGUI/GEventLoop.h b/LibGUI/GEventLoop.h index 325115e70a4..14ea43bd419 100644 --- a/LibGUI/GEventLoop.h +++ b/LibGUI/GEventLoop.h @@ -23,14 +23,15 @@ public: void post_event(GObject& receiver, OwnPtr&&); static GEventLoop& main(); + static GEventLoop& current(); bool running() const { return m_running; } - int register_timer(GObject&, int milliseconds, bool should_reload); - bool unregister_timer(int timer_id); + static int register_timer(GObject&, int milliseconds, bool should_reload); + static bool unregister_timer(int timer_id); - void register_notifier(Badge, GNotifier&); - void unregister_notifier(Badge, GNotifier&); + static void register_notifier(Badge, GNotifier&); + static void unregister_notifier(Badge, GNotifier&); void quit(int); @@ -41,6 +42,12 @@ public: static pid_t server_pid() { return s_server_pid; } + void take_pending_events_from(GEventLoop& other) + { + m_queued_events.append(move(other.m_queued_events)); + m_unprocessed_messages.append(move(other.m_unprocessed_messages)); + } + private: void wait_for_event(); bool drain_messages_from_server(); @@ -67,7 +74,6 @@ private: bool m_running { false }; bool m_exit_requested { false }; int m_exit_code { 0 }; - int m_next_timer_id { 1 }; static pid_t s_server_pid; static pid_t s_event_fd; @@ -83,6 +89,8 @@ private: bool has_expired() const; }; - HashMap> m_timers; - HashTable m_notifiers; + static HashMap>* s_timers; + static int s_next_timer_id; + + static HashTable* s_notifiers; }; diff --git a/LibGUI/GMenu.cpp b/LibGUI/GMenu.cpp index 69ecad518dc..54712228ce1 100644 --- a/LibGUI/GMenu.cpp +++ b/LibGUI/GMenu.cpp @@ -46,7 +46,7 @@ int GMenu::realize_menu() ASSERT(m_name.length() < (ssize_t)sizeof(request.text)); strcpy(request.text, m_name.characters()); request.text_length = m_name.length(); - auto response = GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidCreateMenu); + auto response = GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidCreateMenu); m_menu_id = response.menu.menu_id; ASSERT(m_menu_id > 0); @@ -56,7 +56,7 @@ int GMenu::realize_menu() WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::AddMenuSeparator; request.menu.menu_id = m_menu_id; - GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuSeparator); + GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuSeparator); continue; } if (item.type() == GMenuItem::Action) { @@ -78,7 +78,7 @@ int GMenu::realize_menu() request.menu.shortcut_text_length = 0; } - GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuItem); + GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuItem); } } all_menus().set(m_menu_id, this); @@ -93,7 +93,7 @@ void GMenu::unrealize_menu() WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::DestroyMenu; request.menu.menu_id = m_menu_id; - GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidDestroyMenu); + GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidDestroyMenu); m_menu_id = 0; } diff --git a/LibGUI/GMenuBar.cpp b/LibGUI/GMenuBar.cpp index 1cce445e69d..8befe45944b 100644 --- a/LibGUI/GMenuBar.cpp +++ b/LibGUI/GMenuBar.cpp @@ -19,7 +19,7 @@ int GMenuBar::realize_menubar() { WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::CreateMenubar; - WSAPI_ServerMessage response = GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidCreateMenubar); + WSAPI_ServerMessage response = GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidCreateMenubar); return response.menu.menubar_id; } @@ -30,7 +30,7 @@ void GMenuBar::unrealize_menubar() WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::DestroyMenubar; request.menu.menubar_id = m_menubar_id; - GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidDestroyMenubar); + GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidDestroyMenubar); m_menubar_id = 0; } @@ -47,12 +47,12 @@ void GMenuBar::notify_added_to_application(Badge) request.type = WSAPI_ClientMessage::Type::AddMenuToMenubar; request.menu.menubar_id = m_menubar_id; request.menu.menu_id = menu_id; - GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuToMenubar); + GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuToMenubar); } WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::SetApplicationMenubar; request.menu.menubar_id = m_menubar_id; - GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidSetApplicationMenubar); + GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidSetApplicationMenubar); } void GMenuBar::notify_removed_from_application(Badge) diff --git a/LibGUI/GMessageBox.cpp b/LibGUI/GMessageBox.cpp new file mode 100644 index 00000000000..8f97cca5bdb --- /dev/null +++ b/LibGUI/GMessageBox.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +GMessageBox::GMessageBox(const String& text, const String& title, GObject* parent) + : GDialog(parent) + , m_text(text) +{ + set_title(title); + build(); +} + +GMessageBox::~GMessageBox() +{ +} + +void GMessageBox::build() +{ + auto* widget = new GWidget; + set_main_widget(widget); + + int text_width = widget->font().width(m_text); + + set_rect(x(), y(), text_width + 80, 80); + + widget->set_layout(make(Orientation::Vertical)); + widget->set_fill_with_background_color(true); + + widget->layout()->set_margins({ 0, 15, 0, 15 }); + widget->layout()->set_spacing(15); + + auto* label = new GLabel(m_text, widget); + label->set_size_policy(SizePolicy::Fixed, SizePolicy::Fixed); + label->set_preferred_size({ text_width, 16 }); + + auto* button = new GButton(widget); + button->set_size_policy(SizePolicy::Fixed, SizePolicy::Fixed); + button->set_preferred_size({ 100, 16 }); + button->set_caption("OK"); + button->on_click = [this] (auto&) { + dbgprintf("OK button clicked\n"); + done(0); + }; +} diff --git a/LibGUI/GMessageBox.h b/LibGUI/GMessageBox.h new file mode 100644 index 00000000000..fbbc371fba5 --- /dev/null +++ b/LibGUI/GMessageBox.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class GMessageBox : public GDialog { +public: + explicit GMessageBox(const String& text, const String& title, GObject* parent = nullptr); + virtual ~GMessageBox() override; + + String text() const { return m_text; } + + void build(); + +private: + String m_text; +}; diff --git a/LibGUI/GNotifier.cpp b/LibGUI/GNotifier.cpp index 1d5ad6c8164..649cc84bcc0 100644 --- a/LibGUI/GNotifier.cpp +++ b/LibGUI/GNotifier.cpp @@ -5,11 +5,11 @@ GNotifier::GNotifier(int fd, unsigned event_mask) : m_fd(fd) , m_event_mask(event_mask) { - GEventLoop::main().register_notifier(Badge(), *this); + GEventLoop::register_notifier(Badge(), *this); } GNotifier::~GNotifier() { - GEventLoop::main().unregister_notifier(Badge(), *this); + GEventLoop::unregister_notifier(Badge(), *this); } diff --git a/LibGUI/GObject.cpp b/LibGUI/GObject.cpp index 1d2c59b427b..4dbbe4bc0b6 100644 --- a/LibGUI/GObject.cpp +++ b/LibGUI/GObject.cpp @@ -2,6 +2,7 @@ #include "GEvent.h" #include "GEventLoop.h" #include +#include GObject::GObject(GObject* parent) : m_parent(parent) @@ -42,7 +43,7 @@ void GObject::event(GEvent& event) void GObject::add_child(GObject& object) { m_children.append(&object); - GEventLoop::main().post_event(*this, make(GEvent::ChildAdded, object)); + GEventLoop::current().post_event(*this, make(GEvent::ChildAdded, object)); } void GObject::remove_child(GObject& object) @@ -50,7 +51,7 @@ void GObject::remove_child(GObject& object) for (ssize_t i = 0; i < m_children.size(); ++i) { if (m_children[i] == &object) { m_children.remove(i); - GEventLoop::main().post_event(*this, make(GEvent::ChildRemoved, object)); + GEventLoop::current().post_event(*this, make(GEvent::ChildRemoved, object)); return; } } @@ -71,20 +72,32 @@ void GObject::start_timer(int ms) ASSERT_NOT_REACHED(); } - m_timer_id = GEventLoop::main().register_timer(*this, ms, true); + m_timer_id = GEventLoop::register_timer(*this, ms, true); } void GObject::stop_timer() { if (!m_timer_id) return; - bool success = GEventLoop::main().unregister_timer(m_timer_id); + bool success = GEventLoop::unregister_timer(m_timer_id); ASSERT(success); m_timer_id = 0; } void GObject::delete_later() { - GEventLoop::main().post_event(*this, make(GEvent::DeferredDestroy)); + GEventLoop::current().post_event(*this, make(GEvent::DeferredDestroy)); } +void GObject::dump_tree(int indent) +{ + for (int i = 0; i < indent; ++i) { + printf(" "); + } + printf("%s{%p}\n", class_name(), this); + + for (auto* child : children()) { + child->dump_tree(indent + 2); + } + +} diff --git a/LibGUI/GObject.h b/LibGUI/GObject.h index b1f24462fe7..694112dace6 100644 --- a/LibGUI/GObject.h +++ b/LibGUI/GObject.h @@ -30,6 +30,8 @@ public: void delete_later(); + void dump_tree(int indent = 0); + virtual bool is_widget() const { return false; } protected: diff --git a/LibGUI/GWindow.cpp b/LibGUI/GWindow.cpp index 8486489f167..3dd4b9557ae 100644 --- a/LibGUI/GWindow.cpp +++ b/LibGUI/GWindow.cpp @@ -44,10 +44,8 @@ GWindow::~GWindow() void GWindow::close() { - // FIXME: If we exit the event loop, we're never gonna deal with the delete_later request! - // This will become relevant once we support nested event loops. - if (should_exit_app_on_close()) - GEventLoop::main().quit(0); + if (should_exit_event_loop_on_close()) + GEventLoop::current().quit(0); delete_later(); } @@ -67,7 +65,7 @@ void GWindow::show() ASSERT(m_title_when_windowless.length() < (ssize_t)sizeof(request.text)); strcpy(request.text, m_title_when_windowless.characters()); request.text_length = m_title_when_windowless.length(); - auto response = GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidCreateWindow); + auto response = GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidCreateWindow); m_window_id = response.window_id; windows().set(m_window_id, this); @@ -82,12 +80,12 @@ void GWindow::hide() WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::DestroyWindow; request.window_id = m_window_id; - GEventLoop::main().post_message_to_server(request); + GEventLoop::current().post_message_to_server(request); } -void GWindow::set_title(String&& title) +void GWindow::set_title(const String& title) { - m_title_when_windowless = move(title); + m_title_when_windowless = title; if (!m_window_id) return; @@ -97,7 +95,7 @@ void GWindow::set_title(String&& title) ASSERT(m_title_when_windowless.length() < (ssize_t)sizeof(request.text)); strcpy(request.text, m_title_when_windowless.characters()); request.text_length = m_title_when_windowless.length(); - GEventLoop::main().post_message_to_server(request); + GEventLoop::current().post_message_to_server(request); } String GWindow::title() const @@ -108,7 +106,7 @@ String GWindow::title() const WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::GetWindowTitle; request.window_id = m_window_id; - auto response = GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidGetWindowTitle); + auto response = GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidGetWindowTitle); return String(response.text, response.text_length); } @@ -120,7 +118,7 @@ Rect GWindow::rect() const WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::GetWindowRect; request.window_id = m_window_id; - auto response = GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidGetWindowRect); + auto response = GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidGetWindowRect); ASSERT(response.window_id == m_window_id); return response.window.rect; } @@ -128,13 +126,16 @@ Rect GWindow::rect() const void GWindow::set_rect(const Rect& a_rect) { m_rect_when_windowless = a_rect; - if (!m_window_id) + if (!m_window_id) { + if (m_main_widget) + m_main_widget->resize(m_rect_when_windowless.size()); return; + } WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::SetWindowRect; request.window_id = m_window_id; request.window.rect = a_rect; - GEventLoop::main().post_message_to_server(request); + GEventLoop::current().post_message_to_server(request); } void GWindow::event(GEvent& event) @@ -185,7 +186,7 @@ void GWindow::event(GEvent& event) message.type = WSAPI_ClientMessage::Type::DidFinishPainting; message.window_id = m_window_id; message.window.rect = rect; - GEventLoop::main().post_message_to_server(message); + GEventLoop::current().post_message_to_server(message); } return; } @@ -255,7 +256,7 @@ void GWindow::update(const Rect& a_rect) request.type = WSAPI_ClientMessage::Type::InvalidateRect; request.window_id = m_window_id; request.window.rect = a_rect; - GEventLoop::main().post_message_to_server(request); + GEventLoop::current().post_message_to_server(request); } void GWindow::set_main_widget(GWidget* widget) @@ -283,12 +284,12 @@ void GWindow::set_focused_widget(GWidget* widget) if (m_focused_widget == widget) return; if (m_focused_widget) { - GEventLoop::main().post_event(*m_focused_widget, make(GEvent::FocusOut)); + GEventLoop::current().post_event(*m_focused_widget, make(GEvent::FocusOut)); m_focused_widget->update(); } m_focused_widget = widget; if (m_focused_widget) { - GEventLoop::main().post_event(*m_focused_widget, make(GEvent::FocusIn)); + GEventLoop::current().post_event(*m_focused_widget, make(GEvent::FocusIn)); m_focused_widget->update(); } } @@ -306,7 +307,7 @@ void GWindow::set_global_cursor_tracking_widget(GWidget* widget) request.value = widget != nullptr; // FIXME: What if the cursor moves out of our interest range before the server can handle this? // Maybe there could be a response that includes the current cursor location as of enabling. - GEventLoop::main().post_message_to_server(request); + GEventLoop::current().post_message_to_server(request); } void GWindow::set_has_alpha_channel(bool value) @@ -331,7 +332,7 @@ void GWindow::set_opacity(float opacity) request.window_id = m_window_id; request.window.opacity = opacity; m_opacity_when_windowless = opacity; - GEventLoop::main().post_message_to_server(request); + GEventLoop::current().post_message_to_server(request); } void GWindow::set_hovered_widget(GWidget* widget) @@ -340,12 +341,12 @@ void GWindow::set_hovered_widget(GWidget* widget) return; if (m_hovered_widget) - GEventLoop::main().post_event(*m_hovered_widget, make(GEvent::Leave)); + GEventLoop::current().post_event(*m_hovered_widget, make(GEvent::Leave)); m_hovered_widget = widget ? widget->make_weak_ptr() : nullptr; if (m_hovered_widget) - GEventLoop::main().post_event(*m_hovered_widget, make(GEvent::Enter)); + GEventLoop::current().post_event(*m_hovered_widget, make(GEvent::Enter)); } void GWindow::set_current_backing_bitmap(GraphicsBitmap& bitmap, bool flush_immediately) @@ -359,7 +360,7 @@ void GWindow::set_current_backing_bitmap(GraphicsBitmap& bitmap, bool flush_imme message.backing.has_alpha_channel = bitmap.has_alpha_channel(); message.backing.size = bitmap.size(); message.backing.flush_immediately = flush_immediately; - GEventLoop::main().sync_request(message, WSAPI_ServerMessage::Type::DidSetWindowBackingStore); + GEventLoop::current().sync_request(message, WSAPI_ServerMessage::Type::DidSetWindowBackingStore); } void GWindow::flip(const Rect& dirty_rect) @@ -389,3 +390,9 @@ Retained GWindow::create_backing_bitmap(const Size& size) auto format = m_has_alpha_channel ? GraphicsBitmap::Format::RGBA32 : GraphicsBitmap::Format::RGB32; return GraphicsBitmap::create_with_shared_buffer(format, *shared_buffer, size); } + +void GWindow::set_modal(bool modal) +{ + ASSERT(!m_window_id); + m_modal = modal; +} diff --git a/LibGUI/GWindow.h b/LibGUI/GWindow.h index 16abab6cbfe..3874887c5f8 100644 --- a/LibGUI/GWindow.h +++ b/LibGUI/GWindow.h @@ -15,6 +15,9 @@ public: static GWindow* from_window_id(int); + bool is_modal() const { return m_modal; } + void set_modal(bool); + void set_double_buffering_enabled(bool); void set_has_alpha_channel(bool); void set_opacity(float); @@ -22,7 +25,7 @@ public: int window_id() const { return m_window_id; } String title() const; - void set_title(String&&); + void set_title(const String&); int x() const { return rect().x(); } int y() const { return rect().y(); } @@ -62,8 +65,8 @@ public: GWidget* global_cursor_tracking_widget() { return m_global_cursor_tracking_widget.ptr(); } const GWidget* global_cursor_tracking_widget() const { return m_global_cursor_tracking_widget.ptr(); } - bool should_exit_app_on_close() const { return m_should_exit_app_on_close; } - void set_should_exit_app_on_close(bool b) { m_should_exit_app_on_close = b; } + bool should_exit_event_loop_on_close() const { return m_should_exit_app_on_close; } + void set_should_exit_event_loop_on_close(bool b) { m_should_exit_app_on_close = b; } GWidget* hovered_widget() { return m_hovered_widget.ptr(); } const GWidget* hovered_widget() const { return m_hovered_widget.ptr(); } @@ -101,5 +104,6 @@ private: bool m_should_exit_app_on_close { false }; bool m_has_alpha_channel { false }; bool m_double_buffering_enabled { true }; + bool m_modal { false }; }; diff --git a/LibGUI/Makefile b/LibGUI/Makefile index 098c3cf779b..025391745a4 100644 --- a/LibGUI/Makefile +++ b/LibGUI/Makefile @@ -42,6 +42,8 @@ LIBGUI_OBJS = \ GScrollableWidget.o \ GSocket.o \ GTCPSocket.o \ + GMessageBox.o \ + GDialog.o \ GWindow.o OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) diff --git a/Userland/guitest2.cpp b/Userland/guitest2.cpp index f91cf451e7c..4e8e593b675 100644 --- a/Userland/guitest2.cpp +++ b/Userland/guitest2.cpp @@ -35,7 +35,7 @@ int main(int argc, char** argv) signal(SIGCHLD, handle_sigchld); auto* launcher_window = make_launcher_window(); - launcher_window->set_should_exit_app_on_close(true); + launcher_window->set_should_exit_event_loop_on_close(true); launcher_window->show(); return app.exec(); diff --git a/WindowServer/WSAPITypes.h b/WindowServer/WSAPITypes.h index bcf075cca9a..65dce5e0af4 100644 --- a/WindowServer/WSAPITypes.h +++ b/WindowServer/WSAPITypes.h @@ -164,6 +164,7 @@ struct WSAPI_ClientMessage { SetWindowBackingStore, GetClipboardContents, SetClipboardContents, + Greeting, }; Type type { Invalid }; int window_id { -1 }; @@ -172,6 +173,9 @@ struct WSAPI_ClientMessage { int value { 0 }; union { + struct { + int client_pid; + } greeting; struct { int menubar_id; int menu_id;