Kaynağa Gözat

LibGUI+WindowServer: Add support for enabled/disabled actions.

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. :^)
Andreas Kling 6 yıl önce
ebeveyn
işleme
054c982181

+ 8 - 3
Applications/TextEditor/main.cpp

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

+ 49 - 0
LibGUI/GAction.cpp

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

+ 22 - 1
LibGUI/GAction.h

@@ -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)
     {
@@ -40,6 +46,14 @@ public:
 
     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);
@@ -47,9 +61,16 @@ private:
     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;
 };
 

+ 36 - 14
LibGUI/GButton.cpp

@@ -2,6 +2,7 @@
 #include <LibGUI/GPainter.h>
 #include <SharedGraphics/StylePainter.h>
 #include <AK/StringBuilder.h>
+#include <LibGUI/GAction.h>
 
 //#define GBUTTON_DEBUG
 
@@ -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)
@@ -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;
@@ -46,8 +49,12 @@ 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);
 }
@@ -55,10 +62,12 @@ void GButton::paint_event(GPaintEvent& event)
 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);
@@ -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);
 }
@@ -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);
 }
@@ -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 - 0
LibGUI/GButton.h

@@ -7,6 +7,8 @@
 #include <SharedGraphics/GraphicsBitmap.h>
 #include <SharedGraphics/TextAlignment.h>
 
+class GAction;
+
 class GButton : public GWidget {
 public:
     explicit GButton(GWidget* parent);
@@ -35,6 +37,8 @@ public:
 
     void click();
 
+    void set_action(GAction&);
+
     virtual const char* class_name() const override { return "GButton"; }
 
 private:
@@ -49,6 +53,7 @@ private:
     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 };

+ 4 - 6
LibGUI/GEventLoop.cpp

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

+ 5 - 2
LibGUI/GMenu.cpp

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

+ 41 - 2
LibGUI/GMenuItem.cpp

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

+ 14 - 2
LibGUI/GMenuItem.h

@@ -1,15 +1,17 @@
 #pragma once
 
 #include <AK/AKString.h>
+#include <AK/Badge.h>
 
 class GAction;
+class GMenu;
 
 class GMenuItem {
 public:
     enum Type { Invalid, Action, Separator };
 
-    explicit GMenuItem(Type);
-    explicit GMenuItem(Retained<GAction>&&);
+    GMenuItem(unsigned menu_id, Type);
+    GMenuItem(unsigned menu_id, Retained<GAction>&&);
     ~GMenuItem();
 
     Type type() const { return m_type; }
@@ -18,9 +20,19 @@ public:
     GAction* action() { return m_action.ptr(); }
     unsigned identifier() const { return m_identifier; }
 
+    bool is_enabled() const { return m_enabled; }
+    void set_enabled(bool);
+
+    void set_menu_id(Badge<GMenu>, unsigned menu_id) { m_menu_id = menu_id; }
+    void set_identifier(Badge<GMenu>, unsigned identifier) { m_identifier = identifier; }
+
 private:
+    void update_window_server();
+
     Type m_type { Invalid };
+    unsigned m_menu_id { 0 };
     unsigned m_identifier { 0 };
+    bool m_enabled { true };
     RetainPtr<GAction> m_action;
 };
 

+ 1 - 0
LibGUI/GToolBar.cpp

@@ -26,6 +26,7 @@ void GToolBar::add_action(Retained<GAction>&& action)
     item->action = move(action);
 
     auto* button = new GButton(this);
+    button->set_action(*item->action);
     button->set_tooltip(item->action->text());
     if (item->action->icon())
         button->set_icon(item->action->icon());

+ 3 - 0
Servers/WindowServer/WSAPITypes.h

@@ -82,6 +82,7 @@ struct WSAPI_ServerMessage {
         DidSetApplicationMenubar,
         DidAddMenuItem,
         DidAddMenuSeparator,
+        DidUpdateMenuItem,
         DidCreateWindow,
         DidDestroyWindow,
         DidGetWindowTitle,
@@ -171,6 +172,7 @@ struct WSAPI_ClientMessage {
         SetApplicationMenubar,
         AddMenuItem,
         AddMenuSeparator,
+        UpdateMenuItem,
         CreateWindow,
         DestroyWindow,
         SetWindowTitle,
@@ -211,6 +213,7 @@ struct WSAPI_ClientMessage {
             unsigned identifier;
             char shortcut_text[32];
             int shortcut_text_length;
+            bool enabled;
         } menu;
         struct {
             WSAPI_Rect rect;

+ 29 - 2
Servers/WindowServer/WSClientConnection.cpp

@@ -215,7 +215,7 @@ void WSClientConnection::handle_request(const WSAPIAddMenuItemRequest& request)
         return;
     }
     auto& menu = *(*it).value;
-    menu.add_item(make<WSMenuItem>(identifier, request.text(), request.shortcut_text()));
+    menu.add_item(make<WSMenuItem>(menu, identifier, request.text(), request.shortcut_text(), request.is_enabled()));
     WSAPI_ServerMessage response;
     response.type = WSAPI_ServerMessage::Type::DidAddMenuItem;
     response.menu.menu_id = menu_id;
@@ -223,6 +223,31 @@ void WSClientConnection::handle_request(const WSAPIAddMenuItemRequest& request)
     post_message(response);
 }
 
+void WSClientConnection::handle_request(const WSAPIUpdateMenuItemRequest& request)
+{
+    int menu_id = request.menu_id();
+    unsigned identifier = request.identifier();
+    auto it = m_menus.find(menu_id);
+    if (it == m_menus.end()) {
+        post_error("WSAPIUpdateMenuItemRequest: Bad menu ID");
+        return;
+    }
+    auto& menu = *(*it).value;
+    auto* menu_item = menu.item_with_identifier(request.identifier());
+    if (!menu_item) {
+        post_error("WSAPIUpdateMenuItemRequest: Bad menu item identifier");
+        return;
+    }
+    menu_item->set_text(request.text());
+    menu_item->set_shortcut_text(request.shortcut_text());
+    menu_item->set_enabled(request.is_enabled());
+    WSAPI_ServerMessage response;
+    response.type = WSAPI_ServerMessage::Type::DidUpdateMenuItem;
+    response.menu.menu_id = menu_id;
+    response.menu.identifier = identifier;
+    post_message(response);
+}
+
 void WSClientConnection::handle_request(const WSAPIAddMenuSeparatorRequest& request)
 {
     int menu_id = request.menu_id();
@@ -232,7 +257,7 @@ void WSClientConnection::handle_request(const WSAPIAddMenuSeparatorRequest& requ
         return;
     }
     auto& menu = *(*it).value;
-    menu.add_item(make<WSMenuItem>(WSMenuItem::Separator));
+    menu.add_item(make<WSMenuItem>(menu, WSMenuItem::Separator));
     WSAPI_ServerMessage response;
     response.type = WSAPI_ServerMessage::Type::DidAddMenuSeparator;
     response.menu.menu_id = menu_id;
@@ -553,6 +578,8 @@ void WSClientConnection::on_request(const WSAPIClientRequest& request)
         return handle_request(static_cast<const WSAPIAddMenuItemRequest&>(request));
     case WSMessage::APIAddMenuSeparatorRequest:
         return handle_request(static_cast<const WSAPIAddMenuSeparatorRequest&>(request));
+    case WSMessage::APIUpdateMenuItemRequest:
+        return handle_request(static_cast<const WSAPIUpdateMenuItemRequest&>(request));
     case WSMessage::APISetWindowTitleRequest:
         return handle_request(static_cast<const WSAPISetWindowTitleRequest&>(request));
     case WSMessage::APIGetWindowTitleRequest:

+ 1 - 0
Servers/WindowServer/WSClientConnection.h

@@ -50,6 +50,7 @@ private:
     void handle_request(const WSAPISetApplicationMenubarRequest&);
     void handle_request(const WSAPIAddMenuToMenubarRequest&);
     void handle_request(const WSAPIAddMenuItemRequest&);
+    void handle_request(const WSAPIUpdateMenuItemRequest&);
     void handle_request(const WSAPIAddMenuSeparatorRequest&);
     void handle_request(const WSAPISetWindowTitleRequest&);
     void handle_request(const WSAPIGetWindowTitleRequest&);

+ 15 - 2
Servers/WindowServer/WSMenu.cpp

@@ -50,7 +50,8 @@ int WSMenu::height() const
 
 void WSMenu::redraw()
 {
-    ASSERT(menu_window());
+    if (!menu_window())
+        return;
     draw();
     menu_window()->invalidate();
 }
@@ -95,6 +96,8 @@ void WSMenu::draw()
                 painter.fill_rect(item->rect(), WSWindowManager::the().menu_selection_color());
                 text_color = Color::White;
             }
+            if (!item->is_enabled())
+                text_color = Color::MidGray;
             painter.draw_text(item->rect().translated(left_padding(), 0), item->text(), TextAlignment::CenterLeft, text_color);
             if (!item->shortcut_text().is_empty()) {
                 painter.draw_text(item->rect().translated(-right_padding(), 0), item->shortcut_text(), TextAlignment::CenterRight, text_color);
@@ -122,7 +125,8 @@ void WSMenu::on_message(const WSMessage& message)
     if (message.type() == WSMessage::MouseUp) {
         if (!m_hovered_item)
             return;
-        did_activate(*m_hovered_item);
+        if (m_hovered_item->is_enabled())
+            did_activate(*m_hovered_item);
         clear_hovered_item();
         return;
     }
@@ -152,6 +156,15 @@ void WSMenu::did_activate(WSMenuItem& item)
         m_client->post_message(message);
 }
 
+WSMenuItem* WSMenu::item_with_identifier(unsigned identifer)
+{
+    for (auto& item : m_items) {
+        if (item->identifier() == identifer)
+            return item.ptr();
+    }
+    return nullptr;
+}
+
 WSMenuItem* WSMenu::item_at(const Point& position)
 {
     for (auto& item : m_items) {

+ 1 - 0
Servers/WindowServer/WSMenu.h

@@ -62,6 +62,7 @@ public:
     void draw();
     const Font& font() const;
 
+    WSMenuItem* item_with_identifier(unsigned);
     WSMenuItem* item_at(const Point&);
     void redraw();
 

+ 16 - 4
Servers/WindowServer/WSMenuItem.cpp

@@ -1,18 +1,30 @@
 #include "WSMenuItem.h"
+#include "WSMenu.h"
 
-WSMenuItem::WSMenuItem(unsigned identifier, const String& text, const String& shortcut_text)
-    : m_type(Text)
+WSMenuItem::WSMenuItem(WSMenu& menu, unsigned identifier, const String& text, const String& shortcut_text, bool enabled)
+    : m_menu(menu)
+    , m_type(Text)
+    , m_enabled(enabled)
     , m_identifier(identifier)
     , m_text(text)
     , m_shortcut_text(shortcut_text)
 {
 }
 
-WSMenuItem::WSMenuItem(Type type)
-    : m_type(type)
+WSMenuItem::WSMenuItem(WSMenu& menu, Type type)
+    : m_menu(menu)
+    , m_type(type)
 {
 }
 
 WSMenuItem::~WSMenuItem()
 {
 }
+
+void WSMenuItem::set_enabled(bool enabled)
+{
+    if (m_enabled == enabled)
+        return;
+    m_enabled = enabled;
+    m_menu.redraw();
+}

+ 11 - 3
Servers/WindowServer/WSMenuItem.h

@@ -4,6 +4,8 @@
 #include <AK/Function.h>
 #include <SharedGraphics/Rect.h>
 
+class WSMenu;
+
 class WSMenuItem {
 public:
     enum Type {
@@ -12,15 +14,20 @@ public:
         Separator,
     };
 
-    explicit WSMenuItem(unsigned identifier, const String& text, const String& shortcut_text = { });
-    explicit WSMenuItem(Type);
+    WSMenuItem(WSMenu&, unsigned identifier, const String& text, const String& shortcut_text = { }, bool enabled = true);
+    WSMenuItem(WSMenu&, Type);
     ~WSMenuItem();
 
     Type type() const { return m_type; }
-    bool enabled() const { return m_enabled; }
+
+    bool is_enabled() const { return m_enabled; }
+    void set_enabled(bool);
 
     String text() const { return m_text; }
+    void set_text(const String& text) { m_text = text; }
+
     String shortcut_text() const { return m_shortcut_text; }
+    void set_shortcut_text(const String& text) { m_shortcut_text = text; }
 
     void set_rect(const Rect& rect) { m_rect = rect; }
     Rect rect() const { return m_rect; }
@@ -28,6 +35,7 @@ public:
     unsigned identifier() const { return m_identifier; }
 
 private:
+    WSMenu& m_menu;
     Type m_type { None };
     bool m_enabled { true };
     unsigned m_identifier { 0 };

+ 31 - 1
Servers/WindowServer/WSMessage.h

@@ -38,6 +38,7 @@ public:
         APIDestroyMenuRequest,
         APIAddMenuItemRequest,
         APIAddMenuSeparatorRequest,
+        APIUpdateMenuItemRequest,
         APICreateWindowRequest,
         APIDestroyWindowRequest,
         APISetWindowTitleRequest,
@@ -218,12 +219,13 @@ private:
 
 class WSAPIAddMenuItemRequest : public WSAPIClientRequest {
 public:
-    WSAPIAddMenuItemRequest(int client_id, int menu_id, unsigned identifier, const String& text, const String& shortcut_text)
+    WSAPIAddMenuItemRequest(int client_id, int menu_id, unsigned identifier, const String& text, const String& shortcut_text, bool enabled)
         : WSAPIClientRequest(WSMessage::APIAddMenuItemRequest, client_id)
         , m_menu_id(menu_id)
         , m_identifier(identifier)
         , m_text(text)
         , m_shortcut_text(shortcut_text)
+        , m_enabled(enabled)
     {
     }
 
@@ -231,12 +233,40 @@ public:
     unsigned identifier() const { return m_identifier; }
     String text() const { return m_text; }
     String shortcut_text() const { return m_shortcut_text; }
+    bool is_enabled() const { return m_enabled; }
 
 private:
     int m_menu_id { 0 };
     unsigned m_identifier { 0 };
     String m_text;
     String m_shortcut_text;
+    bool m_enabled { true };
+};
+
+class WSAPIUpdateMenuItemRequest : public WSAPIClientRequest {
+public:
+    WSAPIUpdateMenuItemRequest(int client_id, int menu_id, unsigned identifier, const String& text, const String& shortcut_text, bool enabled)
+        : WSAPIClientRequest(WSMessage::APIUpdateMenuItemRequest, client_id)
+        , m_menu_id(menu_id)
+        , m_identifier(identifier)
+        , m_text(text)
+        , m_shortcut_text(shortcut_text)
+        , m_enabled(enabled)
+    {
+    }
+
+    int menu_id() const { return m_menu_id; }
+    unsigned identifier() const { return m_identifier; }
+    String text() const { return m_text; }
+    String shortcut_text() const { return m_shortcut_text; }
+    bool is_enabled() const { return m_enabled; }
+
+private:
+    int m_menu_id { 0 };
+    unsigned m_identifier { 0 };
+    String m_text;
+    String m_shortcut_text;
+    bool m_enabled { true };
 };
 
 class WSAPIAddMenuSeparatorRequest : public WSAPIClientRequest {

+ 6 - 1
Servers/WindowServer/WSMessageLoop.cpp

@@ -296,7 +296,12 @@ void WSMessageLoop::on_receive_from_client(int client_id, const WSAPI_ClientMess
     case WSAPI_ClientMessage::Type::AddMenuItem:
         ASSERT(message.text_length < (ssize_t)sizeof(message.text));
         ASSERT(message.menu.shortcut_text_length < (ssize_t)sizeof(message.menu.shortcut_text));
-        post_message(client, make<WSAPIAddMenuItemRequest>(client_id, message.menu.menu_id, message.menu.identifier, String(message.text, message.text_length), String(message.menu.shortcut_text, message.menu.shortcut_text_length)));
+        post_message(client, make<WSAPIAddMenuItemRequest>(client_id, message.menu.menu_id, message.menu.identifier, String(message.text, message.text_length), String(message.menu.shortcut_text, message.menu.shortcut_text_length), message.menu.enabled));
+        break;
+    case WSAPI_ClientMessage::Type::UpdateMenuItem:
+        ASSERT(message.text_length < (ssize_t)sizeof(message.text));
+        ASSERT(message.menu.shortcut_text_length < (ssize_t)sizeof(message.menu.shortcut_text));
+        post_message(client, make<WSAPIUpdateMenuItemRequest>(client_id, message.menu.menu_id, message.menu.identifier, String(message.text, message.text_length), String(message.menu.shortcut_text, message.menu.shortcut_text_length), message.menu.enabled));
         break;
     case WSAPI_ClientMessage::Type::AddMenuSeparator:
         post_message(client, make<WSAPIAddMenuSeparatorRequest>(client_id, message.menu.menu_id));

+ 10 - 10
Servers/WindowServer/WSWindowManager.cpp

@@ -95,16 +95,16 @@ WSWindowManager::WSWindowManager()
     {
         byte system_menu_name[] = { 0xf8, 0 };
         m_system_menu = make<WSMenu>(nullptr, -1, String((const char*)system_menu_name));
-        m_system_menu->add_item(make<WSMenuItem>(0, "Open Terminal..."));
-        m_system_menu->add_item(make<WSMenuItem>(1, "Open ProcessManager..."));
-        m_system_menu->add_item(make<WSMenuItem>(WSMenuItem::Separator));
-        m_system_menu->add_item(make<WSMenuItem>(100, "640x480"));
-        m_system_menu->add_item(make<WSMenuItem>(101, "800x600"));
-        m_system_menu->add_item(make<WSMenuItem>(102, "1024x768"));
-        m_system_menu->add_item(make<WSMenuItem>(103, "1440x900"));
-        m_system_menu->add_item(make<WSMenuItem>(104, "1920x1080"));
-        m_system_menu->add_item(make<WSMenuItem>(WSMenuItem::Separator));
-        m_system_menu->add_item(make<WSMenuItem>(200, "About..."));
+        m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 0, "Open Terminal..."));
+        m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 1, "Open ProcessManager..."));
+        m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, WSMenuItem::Separator));
+        m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 100, "640x480"));
+        m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 101, "800x600"));
+        m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 102, "1024x768"));
+        m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 103, "1440x900"));
+        m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 104, "1920x1080"));
+        m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, WSMenuItem::Separator));
+        m_system_menu->add_item(make<WSMenuItem>(*m_system_menu, 200, "About..."));
         m_system_menu->on_item_activation = [this] (WSMenuItem& item) {
             if (item.identifier() == 0) {
                 if (fork() == 0) {