mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 14:02:46 +00:00 
			
		
		
		
	LibGUI+WindowServer: Add app-global keyboard shortcuts.
This patch adds a GShortcut class. Each GAction can have a GShortcut which will cause the event loop to listen for that key combination app-globally and activate the event in case it's pressed. The shortcut will also be displayed when the action is added to a menu. Use this to hook up Alt+Up with the "open parent directory" action in the FileManager app. :^)
This commit is contained in:
		
							parent
							
								
									5c0fca0a95
								
							
						
					
					
						commit
						596a5ce5a4
					
				
					 17 changed files with 263 additions and 17 deletions
				
			
		|  | @ -37,7 +37,7 @@ int main(int argc, char** argv) | ||||||
|     auto* directory_table_view = new DirectoryTableView(widget); |     auto* directory_table_view = new DirectoryTableView(widget); | ||||||
|     auto* statusbar = new GStatusBar(widget); |     auto* statusbar = new GStatusBar(widget); | ||||||
| 
 | 
 | ||||||
|     auto open_parent_directory_action = GAction::create("Open parent directory", GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/parentdirectory16.rgb", { 16, 16 }), [directory_table_view] (const GAction&) { |     auto open_parent_directory_action = GAction::create("Open parent directory", { Mod_Alt, Key_Up }, GraphicsBitmap::load_from_file(GraphicsBitmap::Format::RGBA32, "/res/icons/parentdirectory16.rgb", { 16, 16 }), [directory_table_view] (const GAction&) { | ||||||
|         directory_table_view->open_parent_directory(); |         directory_table_view->open_parent_directory(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| #include <LibGUI/GAction.h> | #include <LibGUI/GAction.h> | ||||||
|  | #include <LibGUI/GEventLoop.h> | ||||||
| 
 | 
 | ||||||
| GAction::GAction(const String& text, const String& custom_data, Function<void(const GAction&)> on_activation_callback) | GAction::GAction(const String& text, const String& custom_data, Function<void(const GAction&)> on_activation_callback) | ||||||
|     : on_activation(move(on_activation_callback)) |     : on_activation(move(on_activation_callback)) | ||||||
|  | @ -19,8 +20,19 @@ GAction::GAction(const String& text, RetainPtr<GraphicsBitmap>&& icon, Function< | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | GAction::GAction(const String& text, const GShortcut& shortcut, RetainPtr<GraphicsBitmap>&& icon, Function<void(const GAction&)> on_activation_callback) | ||||||
|  |     : on_activation(move(on_activation_callback)) | ||||||
|  |     , m_text(text) | ||||||
|  |     , m_icon(move(icon)) | ||||||
|  |     , m_shortcut(shortcut) | ||||||
|  | { | ||||||
|  |     GEventLoop::register_action_with_shortcut(Badge<GAction>(), *this); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| GAction::~GAction() | GAction::~GAction() | ||||||
| { | { | ||||||
|  |     if (m_shortcut.is_valid()) | ||||||
|  |         GEventLoop::unregister_action_with_shortcut(Badge<GAction>(), *this); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GAction::activate() | void GAction::activate() | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| #include <AK/Retainable.h> | #include <AK/Retainable.h> | ||||||
| #include <AK/Retained.h> | #include <AK/Retained.h> | ||||||
| #include <SharedGraphics/GraphicsBitmap.h> | #include <SharedGraphics/GraphicsBitmap.h> | ||||||
|  | #include <LibGUI/GShortcut.h> | ||||||
| 
 | 
 | ||||||
| class GAction : public Retainable<GAction> { | class GAction : public Retainable<GAction> { | ||||||
| public: | public: | ||||||
|  | @ -20,9 +21,14 @@ public: | ||||||
|     { |     { | ||||||
|         return adopt(*new GAction(text, move(icon), move(callback))); |         return adopt(*new GAction(text, move(icon), move(callback))); | ||||||
|     } |     } | ||||||
|  |     static Retained<GAction> create(const String& text, const GShortcut& shortcut, RetainPtr<GraphicsBitmap>&& icon, Function<void(const GAction&)> callback) | ||||||
|  |     { | ||||||
|  |         return adopt(*new GAction(text, shortcut, move(icon), move(callback))); | ||||||
|  |     } | ||||||
|     ~GAction(); |     ~GAction(); | ||||||
| 
 | 
 | ||||||
|     String text() const { return m_text; } |     String text() const { return m_text; } | ||||||
|  |     GShortcut shortcut() const { return m_shortcut; } | ||||||
|     String custom_data() const { return m_custom_data; } |     String custom_data() const { return m_custom_data; } | ||||||
|     const GraphicsBitmap* icon() const { return m_icon.ptr(); } |     const GraphicsBitmap* icon() const { return m_icon.ptr(); } | ||||||
| 
 | 
 | ||||||
|  | @ -32,11 +38,13 @@ public: | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     GAction(const String& text, Function<void(const GAction&)> = nullptr); |     GAction(const String& text, 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, RetainPtr<GraphicsBitmap>&& icon, Function<void(const GAction&)> = nullptr); | ||||||
|     GAction(const String& text, const String& custom_data = String(), Function<void(const GAction&)> = nullptr); |     GAction(const String& text, const String& custom_data = String(), Function<void(const GAction&)> = nullptr); | ||||||
| 
 | 
 | ||||||
|     String m_text; |     String m_text; | ||||||
|     String m_custom_data; |     String m_custom_data; | ||||||
|     RetainPtr<GraphicsBitmap> m_icon; |     RetainPtr<GraphicsBitmap> m_icon; | ||||||
|  |     GShortcut m_shortcut; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -18,18 +18,17 @@ | ||||||
| 
 | 
 | ||||||
| //#define GEVENTLOOP_DEBUG
 | //#define GEVENTLOOP_DEBUG
 | ||||||
| 
 | 
 | ||||||
|  | static HashMap<GShortcut, GAction*>* g_actions; | ||||||
| static GEventLoop* s_mainGEventLoop; | static GEventLoop* s_mainGEventLoop; | ||||||
| 
 | 
 | ||||||
| void GEventLoop::initialize() |  | ||||||
| { |  | ||||||
|     s_mainGEventLoop = nullptr; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| GEventLoop::GEventLoop() | GEventLoop::GEventLoop() | ||||||
| { | { | ||||||
|     if (!s_mainGEventLoop) |     if (!s_mainGEventLoop) | ||||||
|         s_mainGEventLoop = this; |         s_mainGEventLoop = this; | ||||||
| 
 | 
 | ||||||
|  |     if (!g_actions) | ||||||
|  |         g_actions = new HashMap<GShortcut, GAction*>; | ||||||
|  | 
 | ||||||
|     m_event_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); |     m_event_fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); | ||||||
|     if (m_event_fd < 0) { |     if (m_event_fd < 0) { | ||||||
|         perror("socket"); |         perror("socket"); | ||||||
|  | @ -154,6 +153,14 @@ void GEventLoop::handle_key_event(const WSAPI_ServerMessage& event, GWindow& win | ||||||
| #ifdef GEVENTLOOP_DEBUG | #ifdef GEVENTLOOP_DEBUG | ||||||
|     dbgprintf("WID=%x KeyEvent character=0x%b\n", event.window_id, event.key.character); |     dbgprintf("WID=%x KeyEvent character=0x%b\n", event.window_id, event.key.character); | ||||||
| #endif | #endif | ||||||
|  | 
 | ||||||
|  |     unsigned modifiers = (event.key.alt * Mod_Alt) + (event.key.ctrl * Mod_Ctrl) + (event.key.shift * Mod_Shift); | ||||||
|  |     auto it = g_actions->find(GShortcut(modifiers, (KeyCode)event.key.key)); | ||||||
|  |     if (it != g_actions->end()) { | ||||||
|  |         (*it).value->activate(); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     auto key_event = make<GKeyEvent>(event.type == WSAPI_ServerMessage::Type::KeyDown ? GEvent::KeyDown : GEvent::KeyUp, event.key.key); |     auto key_event = make<GKeyEvent>(event.type == WSAPI_ServerMessage::Type::KeyDown ? GEvent::KeyDown : GEvent::KeyUp, event.key.key); | ||||||
|     key_event->m_alt = event.key.alt; |     key_event->m_alt = event.key.alt; | ||||||
|     key_event->m_ctrl = event.key.ctrl; |     key_event->m_ctrl = event.key.ctrl; | ||||||
|  | @ -448,3 +455,13 @@ WSAPI_ServerMessage GEventLoop::sync_request(const WSAPI_ClientMessage& request, | ||||||
|     ASSERT(success); |     ASSERT(success); | ||||||
|     return response; |     return response; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void GEventLoop::register_action_with_shortcut(Badge<GAction>, GAction& action) | ||||||
|  | { | ||||||
|  |     g_actions->set(action.shortcut(), &action); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void GEventLoop::unregister_action_with_shortcut(Badge<GAction>, GAction& action) | ||||||
|  | { | ||||||
|  |     g_actions->remove(action.shortcut()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,13 +1,14 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include "GEvent.h" |  | ||||||
| #include <AK/Badge.h> | #include <AK/Badge.h> | ||||||
| #include <AK/HashMap.h> | #include <AK/HashMap.h> | ||||||
| #include <AK/OwnPtr.h> | #include <AK/OwnPtr.h> | ||||||
| #include <AK/Vector.h> | #include <AK/Vector.h> | ||||||
| #include <AK/WeakPtr.h> | #include <AK/WeakPtr.h> | ||||||
| #include <WindowServer/WSAPITypes.h> | #include <WindowServer/WSAPITypes.h> | ||||||
|  | #include <LibGUI/GEvent.h> | ||||||
| 
 | 
 | ||||||
|  | class GAction; | ||||||
| class GObject; | class GObject; | ||||||
| class GNotifier; | class GNotifier; | ||||||
| class GWindow; | class GWindow; | ||||||
|  | @ -23,8 +24,6 @@ public: | ||||||
| 
 | 
 | ||||||
|     static GEventLoop& main(); |     static GEventLoop& main(); | ||||||
| 
 | 
 | ||||||
|     static void initialize(); |  | ||||||
| 
 |  | ||||||
|     bool running() const { return m_running; } |     bool running() const { return m_running; } | ||||||
| 
 | 
 | ||||||
|     int register_timer(GObject&, int milliseconds, bool should_reload); |     int register_timer(GObject&, int milliseconds, bool should_reload); | ||||||
|  | @ -42,6 +41,9 @@ public: | ||||||
| 
 | 
 | ||||||
|     pid_t server_pid() const { return m_server_pid; } |     pid_t server_pid() const { return m_server_pid; } | ||||||
| 
 | 
 | ||||||
|  |     static void register_action_with_shortcut(Badge<GAction>, GAction&); | ||||||
|  |     static void unregister_action_with_shortcut(Badge<GAction>, GAction&); | ||||||
|  | 
 | ||||||
| private: | private: | ||||||
|     void wait_for_event(); |     void wait_for_event(); | ||||||
|     bool drain_messages_from_server(); |     bool drain_messages_from_server(); | ||||||
|  |  | ||||||
|  | @ -68,6 +68,16 @@ int GMenu::realize_menu() | ||||||
|             ASSERT(action.text().length() < (ssize_t)sizeof(request.text)); |             ASSERT(action.text().length() < (ssize_t)sizeof(request.text)); | ||||||
|             strcpy(request.text, action.text().characters()); |             strcpy(request.text, action.text().characters()); | ||||||
|             request.text_length = action.text().length(); |             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::main().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuItem); |             GEventLoop::main().sync_request(request, WSAPI_ServerMessage::Type::DidAddMenuItem); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
							
								
								
									
										137
									
								
								LibGUI/GShortcut.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								LibGUI/GShortcut.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,137 @@ | ||||||
|  | #include <LibGUI/GShortcut.h> | ||||||
|  | #include <AK/StringBuilder.h> | ||||||
|  | 
 | ||||||
|  | static String to_string(KeyCode key) | ||||||
|  | { | ||||||
|  |     switch (key) { | ||||||
|  |     case Key_Escape: return "Escape"; | ||||||
|  |     case Key_Tab: return "Tab"; | ||||||
|  |     case Key_Backspace: return "Backspace"; | ||||||
|  |     case Key_Return: return "Return"; | ||||||
|  |     case Key_Insert: return "Insert"; | ||||||
|  |     case Key_Delete: return "Delete"; | ||||||
|  |     case Key_PrintScreen: return "PrintScreen"; | ||||||
|  |     case Key_SysRq: return "SysRq"; | ||||||
|  |     case Key_Home: return "Home"; | ||||||
|  |     case Key_End: return "End"; | ||||||
|  |     case Key_Left: return "Left"; | ||||||
|  |     case Key_Up: return "Up"; | ||||||
|  |     case Key_Right: return "Right"; | ||||||
|  |     case Key_Down: return "Down"; | ||||||
|  |     case Key_PageUp: return "PageUp"; | ||||||
|  |     case Key_PageDown: return "PageDown"; | ||||||
|  |     case Key_Shift: return "Shift"; | ||||||
|  |     case Key_Control: return "Control"; | ||||||
|  |     case Key_Alt: return "Alt"; | ||||||
|  |     case Key_CapsLock: return "CapsLock"; | ||||||
|  |     case Key_NumLock: return "NumLock"; | ||||||
|  |     case Key_ScrollLock: return "ScrollLock"; | ||||||
|  |     case Key_F1: return "F1"; | ||||||
|  |     case Key_F2: return "F2"; | ||||||
|  |     case Key_F3: return "F3"; | ||||||
|  |     case Key_F4: return "F4"; | ||||||
|  |     case Key_F5: return "F5"; | ||||||
|  |     case Key_F6: return "F6"; | ||||||
|  |     case Key_F7: return "F7"; | ||||||
|  |     case Key_F8: return "F8"; | ||||||
|  |     case Key_F9: return "F9"; | ||||||
|  |     case Key_F10: return "F10"; | ||||||
|  |     case Key_F11: return "F11"; | ||||||
|  |     case Key_F12: return "F12"; | ||||||
|  |     case Key_Space: return "Space"; | ||||||
|  |     case Key_ExclamationPoint: return "!"; | ||||||
|  |     case Key_DoubleQuote: return "\""; | ||||||
|  |     case Key_Hashtag: return "#"; | ||||||
|  |     case Key_Dollar: return "$"; | ||||||
|  |     case Key_Percent: return "%"; | ||||||
|  |     case Key_Ampersand: return "&"; | ||||||
|  |     case Key_Apostrophe: return "'"; | ||||||
|  |     case Key_LeftParen: return "("; | ||||||
|  |     case Key_RightParen: return ")"; | ||||||
|  |     case Key_Asterisk: return "*"; | ||||||
|  |     case Key_Plus: return "+"; | ||||||
|  |     case Key_Comma: return ","; | ||||||
|  |     case Key_Minus: return "-"; | ||||||
|  |     case Key_Period: return ","; | ||||||
|  |     case Key_Slash: return "/"; | ||||||
|  |     case Key_0: return "0"; | ||||||
|  |     case Key_1: return "1"; | ||||||
|  |     case Key_2: return "2"; | ||||||
|  |     case Key_3: return "3"; | ||||||
|  |     case Key_4: return "4"; | ||||||
|  |     case Key_5: return "5"; | ||||||
|  |     case Key_6: return "6"; | ||||||
|  |     case Key_7: return "7"; | ||||||
|  |     case Key_8: return "8"; | ||||||
|  |     case Key_9: return "9"; | ||||||
|  |     case Key_Colon: return ":"; | ||||||
|  |     case Key_Semicolon: return ";"; | ||||||
|  |     case Key_LessThan: return "<"; | ||||||
|  |     case Key_Equal: return "="; | ||||||
|  |     case Key_GreaterThan: return ">"; | ||||||
|  |     case Key_QuestionMark: return "?"; | ||||||
|  |     case Key_AtSign: return "@"; | ||||||
|  |     case Key_A: return "A"; | ||||||
|  |     case Key_B: return "B"; | ||||||
|  |     case Key_C: return "C"; | ||||||
|  |     case Key_D: return "D"; | ||||||
|  |     case Key_E: return "E"; | ||||||
|  |     case Key_F: return "F"; | ||||||
|  |     case Key_G: return "G"; | ||||||
|  |     case Key_H: return "H"; | ||||||
|  |     case Key_I: return "I"; | ||||||
|  |     case Key_J: return "J"; | ||||||
|  |     case Key_K: return "K"; | ||||||
|  |     case Key_L: return "L"; | ||||||
|  |     case Key_M: return "M"; | ||||||
|  |     case Key_N: return "N"; | ||||||
|  |     case Key_O: return "O"; | ||||||
|  |     case Key_P: return "P"; | ||||||
|  |     case Key_Q: return "Q"; | ||||||
|  |     case Key_R: return "R"; | ||||||
|  |     case Key_S: return "S"; | ||||||
|  |     case Key_T: return "T"; | ||||||
|  |     case Key_U: return "U"; | ||||||
|  |     case Key_V: return "V"; | ||||||
|  |     case Key_W: return "W"; | ||||||
|  |     case Key_X: return "X"; | ||||||
|  |     case Key_Y: return "Y"; | ||||||
|  |     case Key_Z: return "Z"; | ||||||
|  |     case Key_LeftBracket: return "["; | ||||||
|  |     case Key_RightBracket: return "]"; | ||||||
|  |     case Key_Backslash: return "\\"; | ||||||
|  |     case Key_Circumflex: return "^"; | ||||||
|  |     case Key_Underscore: return "_"; | ||||||
|  |     case Key_LeftBrace: return "{"; | ||||||
|  |     case Key_RightBrace: return "}"; | ||||||
|  |     case Key_Pipe: return "|"; | ||||||
|  |     case Key_Tilde: return "~"; | ||||||
|  |     case Key_Backtick: return "`"; | ||||||
|  | 
 | ||||||
|  |     case Key_Invalid: return "Invalid"; | ||||||
|  |     default: | ||||||
|  |         ASSERT_NOT_REACHED(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | String GShortcut::to_string() const | ||||||
|  | { | ||||||
|  |     Vector<String> parts; | ||||||
|  | 
 | ||||||
|  |     if (m_modifiers & Mod_Ctrl) | ||||||
|  |         parts.append("Ctrl"); | ||||||
|  |     if (m_modifiers & Mod_Shift) | ||||||
|  |         parts.append("Shift"); | ||||||
|  |     if (m_modifiers & Mod_Alt) | ||||||
|  |         parts.append("Alt"); | ||||||
|  | 
 | ||||||
|  |     parts.append(::to_string(m_key)); | ||||||
|  | 
 | ||||||
|  |     StringBuilder builder; | ||||||
|  |     for (int i = 0; i < parts.size(); ++i) { | ||||||
|  |         builder.append(parts[i]); | ||||||
|  |         if (i != parts.size() - 1) | ||||||
|  |             builder.append('+'); | ||||||
|  |     } | ||||||
|  |     return builder.to_string(); | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								LibGUI/GShortcut.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								LibGUI/GShortcut.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <Kernel/KeyCode.h> | ||||||
|  | #include <AK/AKString.h> | ||||||
|  | #include <AK/Traits.h> | ||||||
|  | 
 | ||||||
|  | class GShortcut { | ||||||
|  | public: | ||||||
|  |     GShortcut() { } | ||||||
|  |     GShortcut(unsigned modifiers, KeyCode key) | ||||||
|  |         : m_modifiers(modifiers) | ||||||
|  |         , m_key(key) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool is_valid() const { return m_key != KeyCode::Key_Invalid; } | ||||||
|  |     unsigned modifiers() const { return m_modifiers; } | ||||||
|  |     KeyCode key() const { return m_key; } | ||||||
|  |     String to_string() const; | ||||||
|  | 
 | ||||||
|  |     bool operator==(const GShortcut& other) const | ||||||
|  |     { | ||||||
|  |         return m_modifiers == other.m_modifiers | ||||||
|  |             && m_key == other.m_key; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     unsigned m_modifiers { 0 }; | ||||||
|  |     KeyCode m_key { KeyCode::Key_Invalid }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | namespace AK { | ||||||
|  | 
 | ||||||
|  | template<> | ||||||
|  | struct Traits<GShortcut> { | ||||||
|  |     static unsigned hash(const GShortcut& shortcut) | ||||||
|  |     { | ||||||
|  |         return pair_int_hash(shortcut.modifiers(), shortcut.key()); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -31,6 +31,7 @@ LIBGUI_OBJS = \ | ||||||
|     GTableView.o \
 |     GTableView.o \
 | ||||||
|     GTableModel.o \
 |     GTableModel.o \
 | ||||||
|     GVariant.o \
 |     GVariant.o \
 | ||||||
|  |     GShortcut.o \
 | ||||||
|     GWindow.o |     GWindow.o | ||||||
| 
 | 
 | ||||||
| OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) | OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) | ||||||
|  |  | ||||||
|  | @ -166,6 +166,8 @@ struct WSAPI_ClientMessage { | ||||||
|             int menubar_id; |             int menubar_id; | ||||||
|             int menu_id; |             int menu_id; | ||||||
|             unsigned identifier; |             unsigned identifier; | ||||||
|  |             char shortcut_text[32]; | ||||||
|  |             int shortcut_text_length; | ||||||
|         } menu; |         } menu; | ||||||
|         struct { |         struct { | ||||||
|             WSAPI_Rect rect; |             WSAPI_Rect rect; | ||||||
|  |  | ||||||
|  | @ -210,14 +210,13 @@ void WSClientConnection::handle_request(WSAPIAddMenuItemRequest& request) | ||||||
| { | { | ||||||
|     int menu_id = request.menu_id(); |     int menu_id = request.menu_id(); | ||||||
|     unsigned identifier = request.identifier(); |     unsigned identifier = request.identifier(); | ||||||
|     String text = request.text(); |  | ||||||
|     auto it = m_menus.find(menu_id); |     auto it = m_menus.find(menu_id); | ||||||
|     if (it == m_menus.end()) { |     if (it == m_menus.end()) { | ||||||
|         post_error("Bad menu ID"); |         post_error("Bad menu ID"); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     auto& menu = *(*it).value; |     auto& menu = *(*it).value; | ||||||
|     menu.add_item(make<WSMenuItem>(identifier, move(text))); |     menu.add_item(make<WSMenuItem>(identifier, request.text(), request.shortcut_text())); | ||||||
|     WSAPI_ServerMessage response; |     WSAPI_ServerMessage response; | ||||||
|     response.type = WSAPI_ServerMessage::Type::DidAddMenuItem; |     response.type = WSAPI_ServerMessage::Type::DidAddMenuItem; | ||||||
|     response.menu.menu_id = menu_id; |     response.menu.menu_id = menu_id; | ||||||
|  |  | ||||||
|  | @ -29,8 +29,13 @@ int WSMenu::width() const | ||||||
| { | { | ||||||
|     int longest = 0; |     int longest = 0; | ||||||
|     for (auto& item : m_items) { |     for (auto& item : m_items) { | ||||||
|         if (item->type() == WSMenuItem::Text) |         if (item->type() == WSMenuItem::Text) { | ||||||
|             longest = max(longest, font().width(item->text())); |             int item_width = font().width(item->text()); | ||||||
|  |             if (!item->shortcut_text().is_empty()) | ||||||
|  |                 item_width += padding_between_text_and_shortcut() + font().width(item->shortcut_text()); | ||||||
|  | 
 | ||||||
|  |             longest = max(longest, item_width); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return max(longest, rect_in_menubar().width()) + horizontal_padding(); |     return max(longest, rect_in_menubar().width()) + horizontal_padding(); | ||||||
|  | @ -91,6 +96,9 @@ void WSMenu::draw() | ||||||
|                 text_color = Color::White; |                 text_color = Color::White; | ||||||
|             } |             } | ||||||
|             painter.draw_text(item->rect().translated(left_padding(), 0), item->text(), TextAlignment::CenterLeft, text_color); |             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); | ||||||
|  |             } | ||||||
|         } else if (item->type() == WSMenuItem::Separator) { |         } else if (item->type() == WSMenuItem::Separator) { | ||||||
|             Point p1(1, item->rect().center().y()); |             Point p1(1, item->rect().center().y()); | ||||||
|             Point p2(width() - 2, item->rect().center().y()); |             Point p2(width() - 2, item->rect().center().y()); | ||||||
|  |  | ||||||
|  | @ -73,6 +73,7 @@ public: | ||||||
|     void close(); |     void close(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  |     int padding_between_text_and_shortcut() const { return 50; } | ||||||
|     void did_activate(WSMenuItem&); |     void did_activate(WSMenuItem&); | ||||||
|     WSClientConnection* m_client { nullptr }; |     WSClientConnection* m_client { nullptr }; | ||||||
|     int m_menu_id { 0 }; |     int m_menu_id { 0 }; | ||||||
|  |  | ||||||
|  | @ -1,9 +1,10 @@ | ||||||
| #include "WSMenuItem.h" | #include "WSMenuItem.h" | ||||||
| 
 | 
 | ||||||
| WSMenuItem::WSMenuItem(unsigned identifier, const String& text) | WSMenuItem::WSMenuItem(unsigned identifier, const String& text, const String& shortcut_text) | ||||||
|     : m_type(Text) |     : m_type(Text) | ||||||
|     , m_identifier(identifier) |     , m_identifier(identifier) | ||||||
|     , m_text(text) |     , m_text(text) | ||||||
|  |     , m_shortcut_text(shortcut_text) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ public: | ||||||
|         Separator, |         Separator, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     explicit WSMenuItem(unsigned identifier, const String& text); |     explicit WSMenuItem(unsigned identifier, const String& text, const String& shortcut_text = { }); | ||||||
|     explicit WSMenuItem(Type); |     explicit WSMenuItem(Type); | ||||||
|     ~WSMenuItem(); |     ~WSMenuItem(); | ||||||
| 
 | 
 | ||||||
|  | @ -20,6 +20,7 @@ public: | ||||||
|     bool enabled() const { return m_enabled; } |     bool enabled() const { return m_enabled; } | ||||||
| 
 | 
 | ||||||
|     String text() const { return m_text; } |     String text() const { return m_text; } | ||||||
|  |     String shortcut_text() const { return m_shortcut_text; } | ||||||
| 
 | 
 | ||||||
|     void set_rect(const Rect& rect) { m_rect = rect; } |     void set_rect(const Rect& rect) { m_rect = rect; } | ||||||
|     Rect rect() const { return m_rect; } |     Rect rect() const { return m_rect; } | ||||||
|  | @ -31,6 +32,7 @@ private: | ||||||
|     bool m_enabled { true }; |     bool m_enabled { true }; | ||||||
|     unsigned m_identifier { 0 }; |     unsigned m_identifier { 0 }; | ||||||
|     String m_text; |     String m_text; | ||||||
|  |     String m_shortcut_text; | ||||||
|     Rect m_rect; |     Rect m_rect; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -189,22 +189,25 @@ private: | ||||||
| 
 | 
 | ||||||
| class WSAPIAddMenuItemRequest : public WSAPIClientRequest { | class WSAPIAddMenuItemRequest : public WSAPIClientRequest { | ||||||
| public: | public: | ||||||
|     WSAPIAddMenuItemRequest(int client_id, int menu_id, unsigned identifier, const String& text) |     WSAPIAddMenuItemRequest(int client_id, int menu_id, unsigned identifier, const String& text, const String& shortcut_text) | ||||||
|         : WSAPIClientRequest(WSMessage::APIAddMenuItemRequest, client_id) |         : WSAPIClientRequest(WSMessage::APIAddMenuItemRequest, client_id) | ||||||
|         , m_menu_id(menu_id) |         , m_menu_id(menu_id) | ||||||
|         , m_identifier(identifier) |         , m_identifier(identifier) | ||||||
|         , m_text(text) |         , m_text(text) | ||||||
|  |         , m_shortcut_text(shortcut_text) | ||||||
|     { |     { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     int menu_id() const { return m_menu_id; } |     int menu_id() const { return m_menu_id; } | ||||||
|     unsigned identifier() const { return m_identifier; } |     unsigned identifier() const { return m_identifier; } | ||||||
|     String text() const { return m_text; } |     String text() const { return m_text; } | ||||||
|  |     String shortcut_text() const { return m_shortcut_text; } | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     int m_menu_id { 0 }; |     int m_menu_id { 0 }; | ||||||
|     unsigned m_identifier { 0 }; |     unsigned m_identifier { 0 }; | ||||||
|     String m_text; |     String m_text; | ||||||
|  |     String m_shortcut_text; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class WSAPIAddMenuSeparatorRequest : public WSAPIClientRequest { | class WSAPIAddMenuSeparatorRequest : public WSAPIClientRequest { | ||||||
|  |  | ||||||
|  | @ -294,7 +294,8 @@ void WSMessageLoop::on_receive_from_client(int client_id, const WSAPI_ClientMess | ||||||
|         break; |         break; | ||||||
|     case WSAPI_ClientMessage::Type::AddMenuItem: |     case WSAPI_ClientMessage::Type::AddMenuItem: | ||||||
|         ASSERT(message.text_length < (ssize_t)sizeof(message.text)); |         ASSERT(message.text_length < (ssize_t)sizeof(message.text)); | ||||||
|         post_message(client, make<WSAPIAddMenuItemRequest>(client_id, message.menu.menu_id, message.menu.identifier, String(message.text, message.text_length))); |         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))); | ||||||
|         break; |         break; | ||||||
|     case WSAPI_ClientMessage::Type::CreateWindow: |     case WSAPI_ClientMessage::Type::CreateWindow: | ||||||
|         ASSERT(message.text_length < (ssize_t)sizeof(message.text)); |         ASSERT(message.text_length < (ssize_t)sizeof(message.text)); | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling