Interface Kit: Add support for shortcuts w/o Command

Introduce B_NO_COMMAND_KEY modifier that is used when a
shortcut has no Command modifier, otherwise B_COMMAND_KEY
modifier is added.

This is conceptually different from B_NO_MODIFIERS in that it
allows other modifiers such as B_CONTROL_KEY, B_SHIFT_KEY and
B_OPTION_KEY to be added to a shortcut, but not B_COMMAND_KEY.

This option is required to remove B_COMMAND_KEY because the
Command modifier is assumed to be present on all shortcuts.
This allows us to break this assumption without breaking apps
written before this change and assume B_COMMAND_KEY to be
present even if it has not been explictly added.

Document B_NO_COMMAND_KEY in BMenuItem and BWindow docs.

Fixes #7078

Co-authored-by: X512 <[email protected]>
Co-authored-by: Ryan Leavengood <[email protected]>

Change-Id: If0e20cd848112898f4425ede8c17231dc50deb8a
Reviewed-on: https://review.haiku-os.org/c/haiku/+/7293
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: nephele nephele <[email protected]>
This commit is contained in:
John Scipione
2025-02-02 19:18:35 +00:00
co-authored by X512 Ryan Leavengood
parent a5f92820b7
commit 1e2091ced5
7 changed files with 88 additions and 91 deletions
+6 -4
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2014 Haiku, Inc. All rights reserved.
* Copyright 2014-2025 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
@@ -79,8 +79,9 @@
\param label The text \a label that is displayed.
\param message The BMessage that is sent when the item is selected.
\param shortcut The \a shortcut characters to activate the menu item.
\param modifiers The modifier keys to active the menu item,
\c B_COMMAND_KEY is assumed.
\param modifiers The modifier keys to activate the menu item.
\c B_COMMAND_KEY is assumed, to remove \c B_COMMAND_KEY you
must specify \c B_NO_COMMAND_KEY.
\since BeOS R3
*/
@@ -228,7 +229,8 @@
\param shortcut The ASCII shortcut character to set.
\param modifiers A bitmap mask of modifier keys used to activate
the shortcut.
the shortcut. \c B_COMMAND_KEY is assumed, to remove
\c B_COMMAND_KEY you must specify \c B_NO_COMMAND_KEY.
\since BeOS R3
*/
+6 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2011-2015 Haiku, Inc. All rights reserved.
* Copyright 2011-2025 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
@@ -1307,6 +1307,7 @@
- \c B_OPTION_KEY
- \c B_CONTROL_KEY
- \c B_MENU_KEY
To remove \c B_COMMAND_KEY specify \c B_NO_COMMAND_KEY.
\param item The menu \a item to activate.
\since Haiku R1
@@ -1327,6 +1328,7 @@
- \c B_OPTION_KEY
- \c B_CONTROL_KEY
- \c B_MENU_KEY
To remove \c B_COMMAND_KEY specify \c B_NO_COMMAND_KEY.
\param message The \a message to send when the shortcut is activated. The
BWindow takes ownership of the \a message.
@@ -1350,6 +1352,7 @@
- \c B_OPTION_KEY
- \c B_CONTROL_KEY
- \c B_MENU_KEY
To remove \c B_COMMAND_KEY specify \c B_NO_COMMAND_KEY.
\param message The \a message to send when the shortcut is activated. The
BWindow takes ownership of the \a message.
\param target The handler to send the message to.
@@ -1369,6 +1372,7 @@
- \c B_OPTION_KEY
- \c B_CONTROL_KEY
- \c B_MENU_KEY
To remove \c B_COMMAND_KEY specify \c B_NO_COMMAND_KEY.
\returns \c true if the window has the specified shortcut, \c false
otherwise.
@@ -1390,6 +1394,7 @@
- \c B_OPTION_KEY
- \c B_CONTROL_KEY
- \c B_MENU_KEY
To remove \c B_COMMAND_KEY specify \c B_NO_COMMAND_KEY.
\since BeOS R3
*/
+2 -1
View File
@@ -165,7 +165,8 @@ enum {
B_LEFT_CONTROL_KEY = 0x00001000,
B_RIGHT_CONTROL_KEY = 0x00002000,
B_LEFT_OPTION_KEY = 0x00004000,
B_RIGHT_OPTION_KEY = 0x00008000
B_RIGHT_OPTION_KEY = 0x00008000,
B_NO_COMMAND_KEY = 0x00010000
};
+3 -3
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007, Haiku, Inc. All rights reserved.
* Copyright 2006-2025 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _MENU_ITEM_H
@@ -23,8 +23,7 @@ class BMenuItem : public BArchivable, public BInvoker {
public:
BMenuItem(const char* label, BMessage* message,
char shortcut = 0, uint32 modifiers = 0);
BMenuItem(BMenu* menu,
BMessage* message = NULL);
BMenuItem(BMenu* menu, BMessage* message = NULL);
BMenuItem(BMessage* data);
virtual ~BMenuItem();
@@ -94,6 +93,7 @@ private:
void _DrawShortcutSymbol(bool);
void _DrawSubmenuSymbol();
void _DrawControlChar(char shortcut, BPoint where);
void _DrawFunctionChar(uint32 rawKey, BPoint where);
private:
char* fLabel;
+2 -2
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2001-2015, Haiku, Inc. All rights reserved.
* Copyright 2001-2025 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef _WINDOW_H
@@ -316,7 +316,7 @@ private:
BPoint AlertPosition(const BRect& frame);
virtual BMessage* ConvertToMessage(void* raw, int32 code);
void AddShortcut(uint32 key, uint32 modifiers,
void _AddShortcut(uint32 key, uint32 modifiers,
BMenuItem* item);
BHandler* _DetermineTarget(BMessage* message,
BHandler* target);
+14 -26
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2001-2015 Haiku, Inc. All rights reserved.
* Copyright 2001-2025 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
@@ -60,8 +60,8 @@ static const char* kDeleteShortcutUTF8 = "\xe2\x8c\xa6"; /* B_DELETE U+2326 */
using BPrivate::MenuPrivate;
BMenuItem::BMenuItem(const char* label, BMessage* message, char shortcut,
uint32 modifiers)
BMenuItem::BMenuItem(const char* label, BMessage* message, char shortcut, uint32 modifiers)
{
_InitData();
if (label != NULL)
@@ -70,11 +70,7 @@ BMenuItem::BMenuItem(const char* label, BMessage* message, char shortcut,
SetMessage(message);
fShortcutChar = shortcut;
if (shortcut != 0)
fModifiers = modifiers | B_COMMAND_KEY;
else
fModifiers = 0;
fModifiers = (fShortcutChar != 0 ? modifiers : 0);
}
@@ -163,7 +159,7 @@ BMenuItem::Archive(BMessage* data, bool deep) const
if (status == B_OK && fUserTrigger)
status = data->AddInt32("_user_trig", fUserTrigger);
if (status == B_OK && fShortcutChar) {
if (status == B_OK && fShortcutChar != 0) {
status = data->AddInt32("_shortcut", fShortcutChar);
if (status == B_OK)
status = data->AddInt32("_mods", fModifiers);
@@ -276,20 +272,14 @@ BMenuItem::SetTrigger(char trigger)
void
BMenuItem::SetShortcut(char shortcut, uint32 modifiers)
{
if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0
&& fWindow != NULL) {
if (fShortcutChar != 0 && fWindow != NULL)
fWindow->RemoveShortcut(fShortcutChar, fModifiers);
}
fShortcutChar = shortcut;
fModifiers = (fShortcutChar != 0 ? modifiers : 0);
if (shortcut != 0)
fModifiers = modifiers | B_COMMAND_KEY;
else
fModifiers = 0;
if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
fWindow->AddShortcut(fShortcutChar, fModifiers, this);
if (fShortcutChar != 0 && fWindow != NULL)
fWindow->_AddShortcut(fShortcutChar, fModifiers, this);
if (fSuper != NULL) {
fSuper->InvalidateLayout();
@@ -479,7 +469,7 @@ BMenuItem::Draw()
}
if (layout == B_ITEMS_IN_COLUMN) {
if (fShortcutChar)
if (fShortcutChar != 0)
_DrawShortcutSymbol(privateAccessor.HasSubmenus());
if (Submenu() != NULL)
@@ -576,11 +566,11 @@ BMenuItem::Install(BWindow* window)
fWindow = window;
if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
window->AddShortcut(fShortcutChar, fModifiers, this);
if (fShortcutChar != 0 && fWindow != NULL)
fWindow->_AddShortcut(fShortcutChar, fModifiers, this);
if (!Messenger().IsValid())
SetTarget(window);
SetTarget(fWindow);
}
@@ -632,10 +622,8 @@ BMenuItem::Uninstall()
if (Target() == fWindow)
SetTarget(BMessenger());
if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) != 0
&& fWindow != NULL) {
if (fShortcutChar != 0 && fWindow != NULL)
fWindow->RemoveShortcut(fShortcutChar, fModifiers);
}
fWindow = NULL;
}
+54 -53
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2001-2016 Haiku, Inc. All rights reserved
* Copyright 2001-2025 Haiku, Inc. All rights reserved
* Distributed under the terms of the MIT License.
*
* Authors:
@@ -280,8 +280,7 @@ BWindow::Shortcut::Matches(uint32 key, uint32 modifiers) const
uint32
BWindow::Shortcut::AllowedModifiers()
{
return B_COMMAND_KEY | B_OPTION_KEY | B_SHIFT_KEY | B_CONTROL_KEY
| B_MENU_KEY;
return B_COMMAND_KEY | B_OPTION_KEY | B_SHIFT_KEY | B_CONTROL_KEY | B_MENU_KEY;
}
@@ -289,6 +288,9 @@ BWindow::Shortcut::AllowedModifiers()
uint32
BWindow::Shortcut::PrepareModifiers(uint32 modifiers)
{
if ((modifiers & B_NO_COMMAND_KEY) != 0)
return (modifiers & AllowedModifiers()) & ~B_COMMAND_KEY;
else
return (modifiers & AllowedModifiers()) | B_COMMAND_KEY;
}
@@ -297,7 +299,7 @@ BWindow::Shortcut::PrepareModifiers(uint32 modifiers)
uint32
BWindow::Shortcut::PrepareKey(uint32 key)
{
return BUnicodeChar::ToLower(key);
return BUnicodeChar::ToUpper(key);
}
@@ -413,10 +415,9 @@ BWindow::~BWindow()
delete fTopView;
// remove all remaining shortcuts
int32 shortCutCount = fShortcuts.CountItems();
for (int32 i = 0; i < shortCutCount; i++) {
int32 shortcutCount = fShortcuts.CountItems();
for (int32 i = 0; i < shortcutCount; i++)
delete (Shortcut*)fShortcuts.ItemAtFast(i);
}
// TODO: release other dynamically-allocated objects
free(fTitle);
@@ -1681,8 +1682,9 @@ BWindow::PulseRate() const
}
//! \brief Used by BMenuItem to add its shortcut to the window.
void
BWindow::AddShortcut(uint32 key, uint32 modifiers, BMenuItem* item)
BWindow::_AddShortcut(uint32 key, uint32 modifiers, BMenuItem* item)
{
Shortcut* shortcut = new(std::nothrow) Shortcut(key, modifiers, item);
if (shortcut == NULL)
@@ -1703,14 +1705,12 @@ BWindow::AddShortcut(uint32 key, uint32 modifiers, BMessage* message)
void
BWindow::AddShortcut(uint32 key, uint32 modifiers, BMessage* message,
BHandler* target)
BWindow::AddShortcut(uint32 key, uint32 modifiers, BMessage* message, BHandler* target)
{
if (message == NULL)
return;
Shortcut* shortcut = new(std::nothrow) Shortcut(key, modifiers, message,
target);
Shortcut* shortcut = new(std::nothrow) Shortcut(key, modifiers, message, target);
if (shortcut == NULL)
return;
@@ -1732,13 +1732,10 @@ void
BWindow::RemoveShortcut(uint32 key, uint32 modifiers)
{
Shortcut* shortcut = _FindShortcut(key, modifiers);
if (shortcut != NULL) {
fShortcuts.RemoveItem(shortcut);
if (shortcut != NULL && fShortcuts.RemoveItem(shortcut))
delete shortcut;
} else if ((key == 'q' || key == 'Q') && modifiers == B_COMMAND_KEY) {
// the quit shortcut is a fake shortcut
fNoQuitShortcut = true;
}
else if (key == 'Q' && modifiers == B_COMMAND_KEY)
fNoQuitShortcut = true; // the quit shortcut is a fake shortcut
}
@@ -3226,15 +3223,13 @@ BWindow::_DetermineTarget(BMessage* message, BHandler* target)
{
// if we have a default button, it might want to hear
// about pressing the <enter> key
const int32 kNonLockModifierKeys = B_SHIFT_KEY | B_COMMAND_KEY
| B_CONTROL_KEY | B_OPTION_KEY | B_MENU_KEY;
int32 rawChar;
if (DefaultButton() != NULL
&& message->FindInt32("raw_char", &rawChar) == B_OK
&& rawChar == B_ENTER
&& (modifiers() & kNonLockModifierKeys) == 0)
return DefaultButton();
BButton* defaultButton = DefaultButton();
if (defaultButton != NULL) {
int32 rawChar = message->GetInt32("raw_char", 0);
uint32 mods = modifiers();
if (rawChar == B_ENTER && (mods & Shortcut::AllowedModifiers()) == 0)
return defaultButton;
}
// supposed to fall through
}
case B_UNMAPPED_KEY_DOWN:
@@ -3250,6 +3245,7 @@ BWindow::_DetermineTarget(BMessage* message, BHandler* target)
case B_MOUSE_MOVED:
case B_MOUSE_WHEEL_CHANGED:
case B_MOUSE_IDLE:
{
// is there a token of the view that is currently under the mouse?
int32 token;
if (message->FindInt32("_view_token", &token) == B_OK) {
@@ -3263,6 +3259,7 @@ BWindow::_DetermineTarget(BMessage* message, BHandler* target)
if (fLastMouseMovedView != NULL)
return fLastMouseMovedView;
break;
}
case B_PULSE:
case B_QUIT_REQUESTED:
@@ -3625,7 +3622,7 @@ BWindow::_HandleKeyDown(BMessage* event)
if (!_IsFocusMessage(event))
return false;
const char* bytes = NULL;
const char* bytes;
if (event->FindString("bytes", &bytes) != B_OK)
return false;
@@ -3635,9 +3632,12 @@ BWindow::_HandleKeyDown(BMessage* event)
if (event->FindInt32("modifiers", (int32*)&modifiers) != B_OK)
modifiers = 0;
uint32 rawKey;
if (event->FindInt32("key", (int32*)&rawKey) != B_OK)
rawKey = 0;
// handle BMenuBar key
if (key == B_ESCAPE && (modifiers & B_COMMAND_KEY) != 0
&& fKeyMenuBar != NULL) {
if (key == B_ESCAPE && (modifiers & B_COMMAND_KEY) != 0 && fKeyMenuBar != NULL) {
fKeyMenuBar->StartMenuBar(0, true, false, NULL);
return true;
}
@@ -3650,9 +3650,6 @@ BWindow::_HandleKeyDown(BMessage* event)
return true;
}
int32 rawKey;
event->FindInt32("key", &rawKey);
// Deskbar's Switcher
if ((key == B_TAB || rawKey == 0x11) && (modifiers & B_CONTROL_KEY) != 0) {
_Switcher(rawKey, modifiers, event->HasInt32("be:key_repeat"));
@@ -3663,7 +3660,6 @@ BWindow::_HandleKeyDown(BMessage* event)
if (key == B_ESCAPE && (Flags() & B_CLOSE_ON_ESCAPE) != 0) {
BMessage message(B_QUIT_REQUESTED);
message.AddBool("shortcut", true);
PostMessage(&message);
return true;
}
@@ -3699,12 +3695,10 @@ BWindow::_HandleKeyDown(BMessage* event)
if ((modifiers & B_COMMAND_KEY) != 0) {
// Command+q has been pressed, so, we will quit
// the shortcut mechanism doesn't allow handlers outside the window
if (!fNoQuitShortcut && (key == 'Q' || key == 'q')) {
if (!fNoQuitShortcut && key == 'Q') {
BMessage message(B_QUIT_REQUESTED);
message.AddBool("shortcut", true);
be_app->PostMessage(&message);
// eat the event
return true;
}
@@ -3714,19 +3708,28 @@ BWindow::_HandleKeyDown(BMessage* event)
BTextView* textView = dynamic_cast<BTextView*>(CurrentFocus());
if (textView != NULL) {
textView->KeyDown(bytes, modifiers);
// eat the event
return true;
}
}
}
bool foundShortcut = false;
// Handle B_NO_COMMAND_KEY and B_COMMAND_KEY shortcuts
{
// Pretend that the user opened a menu, to give the subclass a
// chance to update it's menus. This may install new shortcuts,
// chance to update its menus. This may install new shortcuts,
// which is why we have to call it here, before trying to find
// a shortcut for the given key.
MenusBeginning();
Shortcut* shortcut = _FindShortcut(key, modifiers);
if (shortcut != NULL) {
// look for B_NO_COMMAND_KEY shortcut then B_COMMAND_KEY
Shortcut* shortcut = _FindShortcut(key, modifiers | B_NO_COMMAND_KEY);
foundShortcut = shortcut != NULL;
if (!foundShortcut && (modifiers & B_COMMAND_KEY) != 0)
shortcut = _FindShortcut(key, modifiers);
foundShortcut = shortcut != NULL;
if (foundShortcut) {
// TODO: would be nice to move this functionality to
// a Shortcut::Invoke() method - but since BMenu::InvokeItem()
// (and BMenuItem::Invoke()) are private, I didn't want
@@ -3744,19 +3747,19 @@ BWindow::_HandleKeyDown(BMessage* event)
if (shortcut->Message() != NULL) {
BMessage message(*shortcut->Message());
if (message.ReplaceInt64("when", system_time()) != B_OK)
message.AddInt64("when", system_time());
if (message.ReplaceBool("shortcut", true) != B_OK)
message.AddBool("shortcut", true);
PostMessage(&message, target);
}
}
}
MenusEnded();
}
if ((modifiers & B_COMMAND_KEY) != 0 || foundShortcut) {
// we always eat the event if the command key was pressed
return true;
}
@@ -3799,16 +3802,16 @@ BWindow::_KeyboardNavigation()
return;
const char* bytes;
uint32 modifiers;
if (message->FindString("bytes", &bytes) != B_OK || bytes[0] != B_TAB)
return;
message->FindInt32("modifiers", (int32*)&modifiers);
uint32 modifiers;
if (message->FindInt32("modifiers", (int32*)&modifiers) != B_OK)
modifiers = 0;
BView* nextFocus;
int32 jumpGroups = (modifiers & B_OPTION_KEY) != 0
? B_NAVIGABLE_JUMP : B_NAVIGABLE;
if (modifiers & B_SHIFT_KEY)
int32 jumpGroups = (modifiers & B_OPTION_KEY) != 0 ? B_NAVIGABLE_JUMP : B_NAVIGABLE;
if ((modifiers & B_SHIFT_KEY) != 0)
nextFocus = _FindPreviousNavigable(fFocus, jumpGroups);
else
nextFocus = _FindNextNavigable(fFocus, jumpGroups);
@@ -3880,15 +3883,13 @@ BWindow::ConvertToMessage(void* raw, int32 code)
BWindow::Shortcut*
BWindow::_FindShortcut(uint32 key, uint32 modifiers)
{
int32 count = fShortcuts.CountItems();
key = Shortcut::PrepareKey(key);
modifiers = Shortcut::PrepareModifiers(modifiers);
for (int32 index = 0; index < count; index++) {
int32 shortcutCount = fShortcuts.CountItems();
for (int32 index = 0; index < shortcutCount; index++) {
Shortcut* shortcut = (Shortcut*)fShortcuts.ItemAt(index);
if (shortcut->Matches(key, modifiers))
if (shortcut != NULL && shortcut->Matches(key, modifiers))
return shortcut;
}