summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-04-12 02:53:27 +0200
committerAndreas Kling <awesomekling@gmail.com>2019-04-12 02:53:27 +0200
commit054c9821819de22d1522021978959e50f8fc6382 (patch)
tree7673e6d859fe6cfb26c28d944724ef8d53f0be99
parent32e5c8c6890a27acb268cd43f087731e11f99d79 (diff)
downloadserenity-054c9821819de22d1522021978959e50f8fc6382.zip
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. :^)
-rw-r--r--Applications/TextEditor/main.cpp11
-rw-r--r--LibGUI/GAction.cpp49
-rw-r--r--LibGUI/GAction.h23
-rw-r--r--LibGUI/GButton.cpp50
-rw-r--r--LibGUI/GButton.h5
-rw-r--r--LibGUI/GEventLoop.cpp10
-rw-r--r--LibGUI/GMenu.cpp7
-rw-r--r--LibGUI/GMenuItem.cpp43
-rw-r--r--LibGUI/GMenuItem.h16
-rw-r--r--LibGUI/GToolBar.cpp1
-rw-r--r--Servers/WindowServer/WSAPITypes.h3
-rw-r--r--Servers/WindowServer/WSClientConnection.cpp31
-rw-r--r--Servers/WindowServer/WSClientConnection.h1
-rw-r--r--Servers/WindowServer/WSMenu.cpp17
-rw-r--r--Servers/WindowServer/WSMenu.h1
-rw-r--r--Servers/WindowServer/WSMenuItem.cpp20
-rw-r--r--Servers/WindowServer/WSMenuItem.h14
-rw-r--r--Servers/WindowServer/WSMessage.h32
-rw-r--r--Servers/WindowServer/WSMessageLoop.cpp7
-rw-r--r--Servers/WindowServer/WSWindowManager.cpp20
20 files changed, 308 insertions, 53 deletions
diff --git a/Applications/TextEditor/main.cpp b/Applications/TextEditor/main.cpp
index d3aa6d38ec..be7a5c715b 100644
--- a/Applications/TextEditor/main.cpp
+++ b/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);
diff --git a/LibGUI/GAction.cpp b/LibGUI/GAction.cpp
index 5a0106530d..c183340e0e 100644
--- a/LibGUI/GAction.cpp
+++ b/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);
+ });
+}
diff --git a/LibGUI/GAction.h b/LibGUI/GAction.h
index f85cda11f0..f691aeed50 100644
--- a/LibGUI/GAction.h
+++ b/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;
};
diff --git a/LibGUI/GButton.cpp b/LibGUI/GButton.cpp
index 7496042fed..b78f0358fd 100644
--- a/LibGUI/GButton.cpp
+++ b/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());
+}
diff --git a/LibGUI/GButton.h b/LibGUI/GButton.h
index b61ee2888a..a5f1d758f6 100644
--- a/LibGUI/GButton.h
+++ b/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 };
diff --git a/LibGUI/GEventLoop.cpp b/LibGUI/GEventLoop.cpp
index a7502c0b7e..11cac1ab92 100644
--- a/LibGUI/GEventLoop.cpp
+++ b/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));
diff --git a/LibGUI/GMenu.cpp b/LibGUI/GMenu.cpp
index 54712228ce..68feb1d0ce 100644
--- a/LibGUI/GMenu.cpp
+++ b/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();
diff --git a/LibGUI/GMenuItem.cpp b/LibGUI/GMenuItem.cpp
index f15bb44c4f..8cbba6d943 100644
--- a/LibGUI/GMenuItem.cpp
+++ b/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);
+}
diff --git a/LibGUI/GMenuItem.h b/LibGUI/GMenuItem.h
index 9c391d9574..0cda499e2d 100644
--- a/LibGUI/GMenuItem.h
+++ b/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;
};
diff --git a/LibGUI/GToolBar.cpp b/LibGUI/GToolBar.cpp
index 11939e9cd8..5fe39f3c6a 100644
--- a/LibGUI/GToolBar.cpp
+++ b/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());
diff --git a/Servers/WindowServer/WSAPITypes.h b/Servers/WindowServer/WSAPITypes.h
index ef7e091fee..dd243454a5 100644
--- a/Servers/WindowServer/WSAPITypes.h
+++ b/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;
diff --git a/Servers/WindowServer/WSClientConnection.cpp b/Servers/WindowServer/WSClientConnection.cpp
index 6937a3b59d..4231bd7be1 100644
--- a/Servers/WindowServer/WSClientConnection.cpp
+++ b/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:
diff --git a/Servers/WindowServer/WSClientConnection.h b/Servers/WindowServer/WSClientConnection.h
index 1d2e3dd159..1f4a2da3bc 100644
--- a/Servers/WindowServer/WSClientConnection.h
+++ b/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&);
diff --git a/Servers/WindowServer/WSMenu.cpp b/Servers/WindowServer/WSMenu.cpp
index 02eeaa7723..38d2e7b445 100644
--- a/Servers/WindowServer/WSMenu.cpp
+++ b/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) {
diff --git a/Servers/WindowServer/WSMenu.h b/Servers/WindowServer/WSMenu.h
index 03315358a8..b8b46edc14 100644
--- a/Servers/WindowServer/WSMenu.h
+++ b/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();
diff --git a/Servers/WindowServer/WSMenuItem.cpp b/Servers/WindowServer/WSMenuItem.cpp
index 49041e0034..95823c86ac 100644
--- a/Servers/WindowServer/WSMenuItem.cpp
+++ b/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();
+}
diff --git a/Servers/WindowServer/WSMenuItem.h b/Servers/WindowServer/WSMenuItem.h
index e3dce736f8..263114648e 100644
--- a/Servers/WindowServer/WSMenuItem.h
+++ b/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 };
diff --git a/Servers/WindowServer/WSMessage.h b/Servers/WindowServer/WSMessage.h
index 4001ac55b8..58b8f31fef 100644
--- a/Servers/WindowServer/WSMessage.h
+++ b/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 {
diff --git a/Servers/WindowServer/WSMessageLoop.cpp b/Servers/WindowServer/WSMessageLoop.cpp
index 5c123becfa..d2e31a1204 100644
--- a/Servers/WindowServer/WSMessageLoop.cpp
+++ b/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));
diff --git a/Servers/WindowServer/WSWindowManager.cpp b/Servers/WindowServer/WSWindowManager.cpp
index 5d8eafdeea..111e5ed181 100644
--- a/Servers/WindowServer/WSWindowManager.cpp
+++ b/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) {