Skip to content

Commit

Permalink
LibGUI+WindowServer: Add support for enabled/disabled actions.
Browse files Browse the repository at this point in the history
The enabled state of a GAction now propagates both to any toolbar buttons
and any menu items linked to the action. Toolbar buttons are painted in
a grayed out style when disabled. Menu items are gray when disabled. :^)
  • Loading branch information
awesomekling committed Apr 12, 2019
1 parent 32e5c8c commit 054c982
Show file tree
Hide file tree
Showing 20 changed files with 308 additions and 53 deletions.
11 changes: 8 additions & 3 deletions Applications/TextEditor/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,21 @@ int main(int argc, char** argv)

toolbar->add_separator();

toolbar->add_action(move(cut_action));
toolbar->add_action(move(copy_action));
toolbar->add_action(cut_action.copy_ref());
toolbar->add_action(copy_action.copy_ref());
toolbar->add_action(move(paste_action));
toolbar->add_action(move(delete_action));
toolbar->add_action(delete_action.copy_ref());

toolbar->add_separator();

toolbar->add_action(move(undo_action));
toolbar->add_action(move(redo_action));

text_editor->on_selection_change = [&] {
cut_action->set_enabled(text_editor->has_selection());
copy_action->set_enabled(text_editor->has_selection());
};

auto* window = new GWindow;
window->set_title(String::format("TextEditor: %s", path.characters()));
window->set_rect(20, 200, 640, 400);
Expand Down
49 changes: 49 additions & 0 deletions LibGUI/GAction.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include <LibGUI/GAction.h>
#include <LibGUI/GApplication.h>
#include <LibGUI/GButton.h>
#include <LibGUI/GMenuItem.h>

GAction::GAction(const String& text, const String& custom_data, Function<void(const GAction&)> on_activation_callback)
: on_activation(move(on_activation_callback))
Expand Down Expand Up @@ -46,3 +48,50 @@ void GAction::activate()
if (on_activation)
on_activation(*this);
}

void GAction::register_button(Badge<GButton>, GButton& button)
{
m_buttons.set(&button);
}

void GAction::unregister_button(Badge<GButton>, GButton& button)
{
m_buttons.remove(&button);
}

void GAction::register_menu_item(Badge<GMenuItem>, GMenuItem& menu_item)
{
m_menu_items.set(&menu_item);
}

void GAction::unregister_menu_item(Badge<GMenuItem>, GMenuItem& menu_item)
{
m_menu_items.remove(&menu_item);
}

template<typename Callback>
void GAction::for_each_toolbar_button(Callback callback)
{
for (auto& it : m_buttons)
callback(*it);
}

template<typename Callback>
void GAction::for_each_menu_item(Callback callback)
{
for (auto& it : m_menu_items)
callback(*it);
}

void GAction::set_enabled(bool enabled)
{
if (m_enabled == enabled)
return;
m_enabled = enabled;
for_each_toolbar_button([enabled] (GButton& button) {
button.set_enabled(enabled);
});
for_each_menu_item([enabled] (GMenuItem& item) {
item.set_enabled(enabled);
});
}
23 changes: 22 additions & 1 deletion LibGUI/GAction.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@
#include <AK/Function.h>
#include <AK/Retainable.h>
#include <AK/Retained.h>
#include <AK/Weakable.h>
#include <AK/Badge.h>
#include <AK/HashTable.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <LibGUI/GShortcut.h>

class GAction : public Retainable<GAction> {
class GButton;
class GMenuItem;

class GAction : public Retainable<GAction>, public Weakable<GAction> {
public:
static Retained<GAction> create(const String& text, Function<void(const GAction&)> callback)
{
Expand Down Expand Up @@ -40,16 +46,31 @@ class GAction : public Retainable<GAction> {

void activate();

bool is_enabled() const { return m_enabled; }
void set_enabled(bool);

void register_button(Badge<GButton>, GButton&);
void unregister_button(Badge<GButton>, GButton&);
void register_menu_item(Badge<GMenuItem>, GMenuItem&);
void unregister_menu_item(Badge<GMenuItem>, GMenuItem&);

private:
GAction(const String& text, Function<void(const GAction&)> = nullptr);
GAction(const String& text, const GShortcut&, Function<void(const GAction&)> = nullptr);
GAction(const String& text, const GShortcut&, RetainPtr<GraphicsBitmap>&& icon, Function<void(const GAction&)> = nullptr);
GAction(const String& text, RetainPtr<GraphicsBitmap>&& icon, Function<void(const GAction&)> = nullptr);
GAction(const String& text, const String& custom_data = String(), Function<void(const GAction&)> = nullptr);

template<typename Callback> void for_each_toolbar_button(Callback);
template<typename Callback> void for_each_menu_item(Callback);

String m_text;
String m_custom_data;
RetainPtr<GraphicsBitmap> m_icon;
GShortcut m_shortcut;
bool m_enabled { true };

HashTable<GButton*> m_buttons;
HashTable<GMenuItem*> m_menu_items;
};

50 changes: 36 additions & 14 deletions LibGUI/GButton.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <LibGUI/GPainter.h>
#include <SharedGraphics/StylePainter.h>
#include <AK/StringBuilder.h>
#include <LibGUI/GAction.h>

//#define GBUTTON_DEBUG

Expand All @@ -12,6 +13,8 @@ GButton::GButton(GWidget* parent)

GButton::~GButton()
{
if (m_action)
m_action->unregister_button({ }, *this);
}

void GButton::set_caption(const String& caption)
Expand All @@ -35,7 +38,7 @@ void GButton::paint_event(GPaintEvent& event)
GPainter painter(*this);
painter.add_clip_rect(event.rect());

StylePainter::paint_button(painter, rect(), m_button_style, m_being_pressed, m_hovered, m_checkable && m_checked);
StylePainter::paint_button(painter, rect(), m_button_style, m_being_pressed, m_hovered, m_checkable && m_checked, is_enabled());

if (m_caption.is_empty() && !m_icon)
return;
Expand All @@ -46,19 +49,25 @@ void GButton::paint_event(GPaintEvent& event)
content_rect.move_by(1, 1);
icon_location.move_by(1, 1);
}
if (m_icon)
painter.blit(icon_location, *m_icon, m_icon->rect());
if (m_icon) {
if (is_enabled())
painter.blit(icon_location, *m_icon, m_icon->rect());
else
painter.blit_dimmed(icon_location, *m_icon, m_icon->rect());
}
auto& font = (m_checkable && m_checked) ? Font::default_bold_font() : this->font();
painter.draw_text(content_rect, m_caption, font, text_alignment(), foreground_color(), TextElision::Right);
}

void GButton::mousemove_event(GMouseEvent& event)
{
if (event.buttons() == GMouseButton::Left) {
bool being_pressed = rect().contains(event.position());
if (being_pressed != m_being_pressed) {
m_being_pressed = being_pressed;
update();
if (is_enabled()) {
bool being_pressed = rect().contains(event.position());
if (being_pressed != m_being_pressed) {
m_being_pressed = being_pressed;
update();
}
}
}
GWidget::mousemove_event(event);
Expand All @@ -70,14 +79,18 @@ void GButton::mousedown_event(GMouseEvent& event)
dbgprintf("GButton::mouse_down_event: x=%d, y=%d, button=%u\n", event.x(), event.y(), (unsigned)event.button());
#endif
if (event.button() == GMouseButton::Left) {
m_being_pressed = true;
update();
if (is_enabled()) {
m_being_pressed = true;
update();
}
}
GWidget::mousedown_event(event);
}

void GButton::click()
{
if (!is_enabled())
return;
if (on_click)
on_click(*this);
}
Expand All @@ -88,11 +101,13 @@ void GButton::mouseup_event(GMouseEvent& event)
dbgprintf("GButton::mouse_up_event: x=%d, y=%d, button=%u\n", event.x(), event.y(), (unsigned)event.button());
#endif
if (event.button() == GMouseButton::Left) {
bool was_being_pressed = m_being_pressed;
m_being_pressed = false;
update();
if (was_being_pressed)
click();
if (is_enabled()) {
bool was_being_pressed = m_being_pressed;
m_being_pressed = false;
update();
if (was_being_pressed)
click();
}
}
GWidget::mouseup_event(event);
}
Expand All @@ -108,3 +123,10 @@ void GButton::leave_event(CEvent&)
m_hovered = false;
update();
}

void GButton::set_action(GAction& action)
{
m_action = action.make_weak_ptr();
action.register_button({ }, *this);
set_enabled(action.is_enabled());
}
5 changes: 5 additions & 0 deletions LibGUI/GButton.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <SharedGraphics/GraphicsBitmap.h>
#include <SharedGraphics/TextAlignment.h>

class GAction;

class GButton : public GWidget {
public:
explicit GButton(GWidget* parent);
Expand Down Expand Up @@ -35,6 +37,8 @@ class GButton : public GWidget {

void click();

void set_action(GAction&);

virtual const char* class_name() const override { return "GButton"; }

private:
Expand All @@ -49,6 +53,7 @@ class GButton : public GWidget {
RetainPtr<GraphicsBitmap> m_icon;
ButtonStyle m_button_style { ButtonStyle::Normal };
TextAlignment m_text_alignment { TextAlignment::Center };
WeakPtr<GAction> m_action;
bool m_being_pressed { false };
bool m_hovered { false };
bool m_checkable { false };
Expand Down
10 changes: 4 additions & 6 deletions LibGUI/GEventLoop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
//#define GEVENTLOOP_DEBUG
//#define COALESCING_DEBUG

static HashMap<GShortcut, GAction*>* g_actions;
int GEventLoop::s_event_fd = -1;
pid_t GEventLoop::s_server_pid = -1;

Expand Down Expand Up @@ -71,9 +70,6 @@ GEventLoop::GEventLoop()
connected = true;
}

if (!g_actions)
g_actions = new HashMap<GShortcut, GAction*>;

#ifdef GEVENTLOOP_DEBUG
dbgprintf("(%u) GEventLoop constructed :)\n", getpid());
#endif
Expand Down Expand Up @@ -125,8 +121,10 @@ void GEventLoop::handle_key_event(const WSAPI_ServerMessage& event, GWindow& win

if (event.type == WSAPI_ServerMessage::Type::KeyDown) {
if (auto* action = GApplication::the().action_for_key_event(*key_event)) {
action->activate();
return;
if (action->is_enabled()) {
action->activate();
return;
}
}
}
post_event(window, move(key_event));
Expand Down
7 changes: 5 additions & 2 deletions LibGUI/GMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ GMenu::~GMenu()

void GMenu::add_action(Retained<GAction>&& action)
{
m_items.append(make<GMenuItem>(move(action)));
m_items.append(make<GMenuItem>(m_menu_id, move(action)));
}

void GMenu::add_separator()
{
m_items.append(make<GMenuItem>(GMenuItem::Separator));
m_items.append(make<GMenuItem>(m_menu_id, GMenuItem::Separator));
}

int GMenu::realize_menu()
Expand All @@ -52,6 +52,8 @@ int GMenu::realize_menu()
ASSERT(m_menu_id > 0);
for (int i = 0; i < m_items.size(); ++i) {
auto& item = *m_items[i];
item.set_menu_id({ }, m_menu_id);
item.set_identifier({ }, i);
if (item.type() == GMenuItem::Separator) {
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::AddMenuSeparator;
Expand All @@ -65,6 +67,7 @@ int GMenu::realize_menu()
request.type = WSAPI_ClientMessage::Type::AddMenuItem;
request.menu.menu_id = m_menu_id;
request.menu.identifier = i;
request.menu.enabled = action.is_enabled();
ASSERT(action.text().length() < (ssize_t)sizeof(request.text));
strcpy(request.text, action.text().characters());
request.text_length = action.text().length();
Expand Down
43 changes: 41 additions & 2 deletions LibGUI/GMenuItem.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,57 @@
#include <LibGUI/GMenuItem.h>
#include <LibGUI/GAction.h>
#include <LibGUI/GEventLoop.h>
#include <WindowServer/WSAPITypes.h>

GMenuItem::GMenuItem(Type type)
GMenuItem::GMenuItem(unsigned menu_id, Type type)
: m_type(type)
, m_menu_id(menu_id)
{
}

GMenuItem::GMenuItem(Retained<GAction>&& action)
GMenuItem::GMenuItem(unsigned menu_id, Retained<GAction>&& action)
: m_type(Action)
, m_menu_id(menu_id)
, m_action(move(action))
{
m_action->register_menu_item({ }, *this);
m_enabled = m_action->is_enabled();
}

GMenuItem::~GMenuItem()
{
if (m_action)
m_action->unregister_menu_item({ }, *this);
}

void GMenuItem::set_enabled(bool enabled)
{
if (m_enabled == enabled)
return;
m_enabled = enabled;
update_window_server();
}

void GMenuItem::update_window_server()
{
auto& action = *m_action;
WSAPI_ClientMessage request;
request.type = WSAPI_ClientMessage::Type::UpdateMenuItem;
request.menu.menu_id = m_menu_id;
request.menu.identifier = m_identifier;
request.menu.enabled = action.is_enabled();
ASSERT(action.text().length() < (ssize_t)sizeof(request.text));
strcpy(request.text, action.text().characters());
request.text_length = action.text().length();

if (action.shortcut().is_valid()) {
auto shortcut_text = action.shortcut().to_string();
ASSERT(shortcut_text.length() < (ssize_t)sizeof(request.menu.shortcut_text));
strcpy(request.menu.shortcut_text, shortcut_text.characters());
request.menu.shortcut_text_length = shortcut_text.length();
} else {
request.menu.shortcut_text_length = 0;
}

GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidUpdateMenuItem);
}
Loading

0 comments on commit 054c982

Please sign in to comment.