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.
This commit is contained in:
Andreas Kling 2019-03-19 00:01:02 +01:00
parent 55aa819077
commit 57ff293a51
Notes: sideshowbarker 2024-07-19 15:00:43 +09:00
29 changed files with 275 additions and 79 deletions

View file

@ -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);

View file

@ -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<GBoxLayout>(Orientation::Vertical));

View file

@ -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();
}

View file

@ -9,6 +9,7 @@
#include <LibGUI/GAction.h>
#include <LibGUI/GMenu.h>
#include <LibGUI/GMenuBar.h>
#include <LibGUI/GMessageBox.h>
#include <stdio.h>
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");
};

View file

@ -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();

View file

@ -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();

View file

@ -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);

View file

@ -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();

View file

@ -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 && \

View file

@ -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;

View file

@ -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();
}

View file

@ -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<GraphicsBitmap>&& icon) { m_icon = move(icon); }
const GraphicsBitmap* icon() const { return m_icon.ptr(); }

View file

@ -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());
}

26
LibGUI/GDialog.cpp Normal file
View file

@ -0,0 +1,26 @@
#include <LibGUI/GDialog.h>
#include <LibGUI/GEventLoop.h>
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);
}

19
LibGUI/GDialog.h Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <LibGUI/GWindow.h>
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 };
};

View file

@ -21,11 +21,16 @@
static HashMap<GShortcut, GAction*>* g_actions;
static GEventLoop* s_main_event_loop;
static Vector<GEventLoop*>* s_event_loop_stack;
int GEventLoop::s_event_fd = -1;
pid_t GEventLoop::s_server_pid = -1;
HashMap<int, OwnPtr<GEventLoop::EventLoopTimer>>* GEventLoop::s_timers;
HashTable<GNotifier*>* 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<GEventLoop*>;
s_timers = new HashMap<int, OwnPtr<GEventLoop::EventLoopTimer>>;
s_notifiers = new HashTable<GNotifier*>;
}
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>, GNotifier& notifier)
{
m_notifiers.set(&notifier);
s_notifiers->set(&notifier);
}
void GEventLoop::unregister_notifier(Badge<GNotifier>, GNotifier& notifier)
{
m_notifiers.remove(&notifier);
s_notifiers->remove(&notifier);
}
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;
}

View file

@ -23,14 +23,15 @@ public:
void post_event(GObject& receiver, OwnPtr<GEvent>&&);
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>, GNotifier&);
void unregister_notifier(Badge<GNotifier>, GNotifier&);
static void register_notifier(Badge<GNotifier>, GNotifier&);
static void unregister_notifier(Badge<GNotifier>, 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<int, OwnPtr<EventLoopTimer>> m_timers;
HashTable<GNotifier*> m_notifiers;
static HashMap<int, OwnPtr<EventLoopTimer>>* s_timers;
static int s_next_timer_id;
static HashTable<GNotifier*>* s_notifiers;
};

View file

@ -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;
}

View file

@ -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<GApplication>)
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<GApplication>)

45
LibGUI/GMessageBox.cpp Normal file
View file

@ -0,0 +1,45 @@
#include <LibGUI/GMessageBox.h>
#include <LibGUI/GBoxLayout.h>
#include <LibGUI/GLabel.h>
#include <LibGUI/GButton.h>
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<GBoxLayout>(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);
};
}

16
LibGUI/GMessageBox.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include <LibGUI/GDialog.h>
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;
};

View file

@ -5,11 +5,11 @@ GNotifier::GNotifier(int fd, unsigned event_mask)
: m_fd(fd)
, m_event_mask(event_mask)
{
GEventLoop::main().register_notifier(Badge<GNotifier>(), *this);
GEventLoop::register_notifier(Badge<GNotifier>(), *this);
}
GNotifier::~GNotifier()
{
GEventLoop::main().unregister_notifier(Badge<GNotifier>(), *this);
GEventLoop::unregister_notifier(Badge<GNotifier>(), *this);
}

View file

@ -2,6 +2,7 @@
#include "GEvent.h"
#include "GEventLoop.h"
#include <AK/Assertions.h>
#include <stdio.h>
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<GChildEvent>(GEvent::ChildAdded, object));
GEventLoop::current().post_event(*this, make<GChildEvent>(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<GChildEvent>(GEvent::ChildRemoved, object));
GEventLoop::current().post_event(*this, make<GChildEvent>(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>(GEvent::DeferredDestroy));
GEventLoop::current().post_event(*this, make<GEvent>(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);
}
}

View file

@ -30,6 +30,8 @@ public:
void delete_later();
void dump_tree(int indent = 0);
virtual bool is_widget() const { return false; }
protected:

View file

@ -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>(GEvent::FocusOut));
GEventLoop::current().post_event(*m_focused_widget, make<GEvent>(GEvent::FocusOut));
m_focused_widget->update();
}
m_focused_widget = widget;
if (m_focused_widget) {
GEventLoop::main().post_event(*m_focused_widget, make<GEvent>(GEvent::FocusIn));
GEventLoop::current().post_event(*m_focused_widget, make<GEvent>(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>(GEvent::Leave));
GEventLoop::current().post_event(*m_hovered_widget, make<GEvent>(GEvent::Leave));
m_hovered_widget = widget ? widget->make_weak_ptr() : nullptr;
if (m_hovered_widget)
GEventLoop::main().post_event(*m_hovered_widget, make<GEvent>(GEvent::Enter));
GEventLoop::current().post_event(*m_hovered_widget, make<GEvent>(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<GraphicsBitmap> 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;
}

View file

@ -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 };
};

View file

@ -42,6 +42,8 @@ LIBGUI_OBJS = \
GScrollableWidget.o \
GSocket.o \
GTCPSocket.o \
GMessageBox.o \
GDialog.o \
GWindow.o
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)

View file

@ -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();

View file

@ -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;