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 b98b4ca496..045b02681d 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 a3a9eafed3..d7b4ab5c9d 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 bf0511e6da..36f5e1d402 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 d61e56fcf3..1b431ecbd6 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 44aca4d460..616df66b0b 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 14008e63b7..38bfdde050 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 808b14e9b3..a3cba5d296 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 4df7073241..16023de124 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 2929983813..d246871f3f 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 9abea5ed62..8e5ecad875 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 4b1a35b988..9023aaac19 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 d5897a5d7a..3e76476d15 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 d6fe9efba3..9a349a157d 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 0000000000..97f0c103c6 --- /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 0000000000..9dcf88a772 --- /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 2015a65ae7..ed9aba0726 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 325115e70a..14ea43bd41 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 69ecad518d..54712228ce 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 1cce445e69..8befe45944 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 0000000000..8f97cca5bd --- /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 0000000000..fbbc371fba --- /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 1d5ad6c816..649cc84bcc 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 1d2c59b427..4dbbe4bc0b 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 b1f24462fe..694112dace 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 8486489f16..3dd4b9557a 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 16abab6cbf..3874887c5f 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 098c3cf779..025391745a 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 f91cf451e7..4e8e593b67 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 bcf075cca9..65dce5e0af 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;