mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 19:27:45 +00:00
WindowServer: Support Alt+Character menu shortcuts :^)
This patch adds support for opening menus via keyboard shortcuts. Use an ampersand in a menu name to automatically create a keyboard shortcut (Alt + the character following the ampersand.) Menus with an Alt shortcut have a small underline under the shortcut character for discoverability.
This commit is contained in:
parent
89bc655765
commit
d80fb6d9c0
6 changed files with 56 additions and 19 deletions
|
@ -43,12 +43,26 @@
|
||||||
|
|
||||||
namespace WindowServer {
|
namespace WindowServer {
|
||||||
|
|
||||||
Menu::Menu(ClientConnection* client, int menu_id, const String& name)
|
static u32 find_ampersand_shortcut_character(const String& string)
|
||||||
|
{
|
||||||
|
Utf8View utf8_view { string };
|
||||||
|
for (auto it = utf8_view.begin(); it != utf8_view.end(); ++it) {
|
||||||
|
if (*it == '&') {
|
||||||
|
++it;
|
||||||
|
if (it != utf8_view.end() && *it != '&')
|
||||||
|
return *it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu::Menu(ClientConnection* client, int menu_id, String name)
|
||||||
: Core::Object(client)
|
: Core::Object(client)
|
||||||
, m_client(client)
|
, m_client(client)
|
||||||
, m_menu_id(menu_id)
|
, m_menu_id(menu_id)
|
||||||
, m_name(move(name))
|
, m_name(move(name))
|
||||||
{
|
{
|
||||||
|
m_alt_shortcut_character = find_ampersand_shortcut_character(m_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu::~Menu()
|
Menu::~Menu()
|
||||||
|
|
|
@ -47,13 +47,15 @@ class Event;
|
||||||
class Menu final : public Core::Object {
|
class Menu final : public Core::Object {
|
||||||
C_OBJECT(Menu)
|
C_OBJECT(Menu)
|
||||||
public:
|
public:
|
||||||
Menu(ClientConnection*, int menu_id, const String& name);
|
Menu(ClientConnection*, int menu_id, String name);
|
||||||
virtual ~Menu() override;
|
virtual ~Menu() override;
|
||||||
|
|
||||||
ClientConnection* client() { return m_client; }
|
ClientConnection* client() { return m_client; }
|
||||||
const ClientConnection* client() const { return m_client; }
|
const ClientConnection* client() const { return m_client; }
|
||||||
int menu_id() const { return m_menu_id; }
|
int menu_id() const { return m_menu_id; }
|
||||||
|
|
||||||
|
u32 alt_shortcut_character() const { return m_alt_shortcut_character; }
|
||||||
|
|
||||||
bool is_empty() const { return m_items.is_empty(); }
|
bool is_empty() const { return m_items.is_empty(); }
|
||||||
int item_count() const { return m_items.size(); }
|
int item_count() const { return m_items.size(); }
|
||||||
const MenuItem& item(int index) const { return m_items.at(index); }
|
const MenuItem& item(int index) const { return m_items.at(index); }
|
||||||
|
@ -142,6 +144,7 @@ private:
|
||||||
ClientConnection* m_client { nullptr };
|
ClientConnection* m_client { nullptr };
|
||||||
int m_menu_id { 0 };
|
int m_menu_id { 0 };
|
||||||
String m_name;
|
String m_name;
|
||||||
|
u32 m_alt_shortcut_character { 0 };
|
||||||
Gfx::IntRect m_rect_in_window_menubar;
|
Gfx::IntRect m_rect_in_window_menubar;
|
||||||
NonnullOwnPtrVector<MenuItem> m_items;
|
NonnullOwnPtrVector<MenuItem> m_items;
|
||||||
RefPtr<Window> m_menu_window;
|
RefPtr<Window> m_menu_window;
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "WindowManager.h"
|
#include "WindowManager.h"
|
||||||
#include <AK/Badge.h>
|
#include <AK/Badge.h>
|
||||||
#include <WindowServer/WindowClientEndpoint.h>
|
#include <WindowServer/WindowClientEndpoint.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
namespace WindowServer {
|
namespace WindowServer {
|
||||||
|
|
||||||
|
@ -445,12 +446,7 @@ void Window::event(Core::Event& event)
|
||||||
m_client->post_message(Messages::WindowClient::WindowLeft(m_window_id));
|
m_client->post_message(Messages::WindowClient::WindowLeft(m_window_id));
|
||||||
break;
|
break;
|
||||||
case Event::KeyDown:
|
case Event::KeyDown:
|
||||||
m_client->post_message(
|
handle_keydown_event(static_cast<const KeyEvent&>(event));
|
||||||
Messages::WindowClient::KeyDown(m_window_id,
|
|
||||||
(u32) static_cast<const KeyEvent&>(event).code_point(),
|
|
||||||
(u32) static_cast<const KeyEvent&>(event).key(),
|
|
||||||
static_cast<const KeyEvent&>(event).modifiers(),
|
|
||||||
(u32) static_cast<const KeyEvent&>(event).scancode()));
|
|
||||||
break;
|
break;
|
||||||
case Event::KeyUp:
|
case Event::KeyUp:
|
||||||
m_client->post_message(
|
m_client->post_message(
|
||||||
|
@ -483,6 +479,25 @@ void Window::event(Core::Event& event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::handle_keydown_event(const KeyEvent& event)
|
||||||
|
{
|
||||||
|
if (event.modifiers() == Mod_Alt && event.code_point() && menubar()) {
|
||||||
|
Menu* menu_to_open = nullptr;
|
||||||
|
menubar()->for_each_menu([&](Menu& menu) {
|
||||||
|
if (tolower(menu.alt_shortcut_character()) == tolower(event.code_point())) {
|
||||||
|
menu_to_open = &menu;
|
||||||
|
return IterationDecision::Break;
|
||||||
|
}
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
if (menu_to_open) {
|
||||||
|
frame().open_menubar_menu(*menu_to_open);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_client->post_message(Messages::WindowClient::KeyDown(m_window_id, (u32)event.code_point(), (u32)event.key(), event.modifiers(), (u32)event.scancode()));
|
||||||
|
}
|
||||||
|
|
||||||
void Window::set_global_cursor_tracking_enabled(bool enabled)
|
void Window::set_global_cursor_tracking_enabled(bool enabled)
|
||||||
{
|
{
|
||||||
m_global_cursor_tracking_enabled = enabled;
|
m_global_cursor_tracking_enabled = enabled;
|
||||||
|
|
|
@ -41,6 +41,7 @@ namespace WindowServer {
|
||||||
|
|
||||||
class ClientConnection;
|
class ClientConnection;
|
||||||
class Cursor;
|
class Cursor;
|
||||||
|
class KeyEvent;
|
||||||
class Menu;
|
class Menu;
|
||||||
class MenuBar;
|
class MenuBar;
|
||||||
class MenuItem;
|
class MenuItem;
|
||||||
|
@ -339,6 +340,7 @@ public:
|
||||||
private:
|
private:
|
||||||
virtual void event(Core::Event&) override;
|
virtual void event(Core::Event&) override;
|
||||||
void handle_mouse_event(const MouseEvent&);
|
void handle_mouse_event(const MouseEvent&);
|
||||||
|
void handle_keydown_event(const KeyEvent&);
|
||||||
void update_menu_item_text(PopupMenuItem item);
|
void update_menu_item_text(PopupMenuItem item);
|
||||||
void update_menu_item_enabled(PopupMenuItem item);
|
void update_menu_item_enabled(PopupMenuItem item);
|
||||||
void add_child_window(Window&);
|
void add_child_window(Window&);
|
||||||
|
|
|
@ -317,12 +317,7 @@ void WindowFrame::paint_menubar(Gfx::Painter& painter)
|
||||||
if (paint_as_pressed || paint_as_hovered) {
|
if (paint_as_pressed || paint_as_hovered) {
|
||||||
Gfx::StylePainter::paint_button(painter, menu.rect_in_window_menubar(), palette, Gfx::ButtonStyle::CoolBar, paint_as_pressed, paint_as_hovered);
|
Gfx::StylePainter::paint_button(painter, menu.rect_in_window_menubar(), palette, Gfx::ButtonStyle::CoolBar, paint_as_pressed, paint_as_hovered);
|
||||||
}
|
}
|
||||||
painter.draw_text(
|
painter.draw_ui_text(menu.name(), text_rect, font, text_color);
|
||||||
text_rect,
|
|
||||||
menu.name(),
|
|
||||||
font,
|
|
||||||
Gfx::TextAlignment::Center,
|
|
||||||
text_color);
|
|
||||||
return IterationDecision::Continue;
|
return IterationDecision::Continue;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -749,6 +744,16 @@ void WindowFrame::handle_menubar_mouse_event(const MouseEvent& event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WindowFrame::open_menubar_menu(Menu& menu)
|
||||||
|
{
|
||||||
|
auto menubar_rect = this->menubar_rect();
|
||||||
|
MenuManager::the().close_everyone();
|
||||||
|
menu.ensure_menu_window().move_to(menu.rect_in_window_menubar().bottom_left().translated(rect().location()).translated(menubar_rect.location()));
|
||||||
|
MenuManager::the().open_menu(menu);
|
||||||
|
WindowManager::the().set_window_with_active_menu(&m_window);
|
||||||
|
invalidate(menubar_rect);
|
||||||
|
}
|
||||||
|
|
||||||
void WindowFrame::handle_menu_mouse_event(Menu& menu, const MouseEvent& event)
|
void WindowFrame::handle_menu_mouse_event(Menu& menu, const MouseEvent& event)
|
||||||
{
|
{
|
||||||
auto menubar_rect = this->menubar_rect();
|
auto menubar_rect = this->menubar_rect();
|
||||||
|
@ -758,11 +763,7 @@ void WindowFrame::handle_menu_mouse_event(Menu& menu, const MouseEvent& event)
|
||||||
bool should_close_menu = &menu == MenuManager::the().current_menu() && is_mousedown_with_left_button;
|
bool should_close_menu = &menu == MenuManager::the().current_menu() && is_mousedown_with_left_button;
|
||||||
|
|
||||||
if (should_open_menu) {
|
if (should_open_menu) {
|
||||||
MenuManager::the().close_everyone();
|
open_menubar_menu(menu);
|
||||||
menu.ensure_menu_window().move_to(menu.rect_in_window_menubar().bottom_left().translated(rect().location()).translated(menubar_rect.location()));
|
|
||||||
MenuManager::the().open_menu(menu);
|
|
||||||
WindowManager::the().set_window_with_active_menu(&m_window);
|
|
||||||
invalidate(menubar_rect);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,8 @@ public:
|
||||||
|
|
||||||
bool hit_test(const Gfx::IntPoint&) const;
|
bool hit_test(const Gfx::IntPoint&) const;
|
||||||
|
|
||||||
|
void open_menubar_menu(Menu&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void paint_simple_rect_shadow(Gfx::Painter&, const Gfx::IntRect&, const Gfx::Bitmap&) const;
|
void paint_simple_rect_shadow(Gfx::Painter&, const Gfx::IntRect&, const Gfx::Bitmap&) const;
|
||||||
void paint_notification_frame(Gfx::Painter&);
|
void paint_notification_frame(Gfx::Painter&);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue