* Copyright 2011-2023 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* John Scipione, jscipione@gmail.com
* Jorge Acereda, jacereda@gmail.com
*/
#include "ModifierKeysWindow.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <FindDirectory.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Message.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Resources.h>
#include <SeparatorView.h>
#include <Size.h>
#include <StringView.h>
#include "KeymapApplication.h"
#include "StatusMenuField.h"
enum {
CAPS_KEY = 0x00000001,
SHIFT_KEY = 0x00000002,
CONTROL_KEY = 0x00000004,
OPTION_KEY = 0x00000008,
COMMAND_KEY = 0x00000010,
};
enum {
MENU_ITEM_CAPS,
MENU_ITEM_SHIFT,
MENU_ITEM_CONTROL,
MENU_ITEM_OPTION,
MENU_ITEM_COMMAND,
MENU_ITEM_SEPARATOR,
MENU_ITEM_DISABLED,
MENU_ITEM_FIRST = MENU_ITEM_CAPS,
MENU_ITEM_LAST = MENU_ITEM_DISABLED
};
static const uint32 kMsgUpdateStatus = 'stat';
static const uint32 kMsgUpdateModifier = 'upmd';
static const uint32 kMsgApplyModifiers = 'apmd';
static const uint32 kMsgRevertModifiers = 'rvmd';
static const int32 kUnset = 0;
static const int32 kDisabled = -1;
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Modifier keys window"
ModifierKeysWindow::ModifierKeysWindow()
:
BWindow(BRect(0, 0, 360, 220), B_TRANSLATE("Modifier keys"),
B_FLOATING_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS)
{
get_key_map(&fCurrentMap, &fCurrentBuffer);
get_key_map(&fSavedMap, &fSavedBuffer);
BStringView* keyRole = new BStringView("key role",
B_TRANSLATE_COMMENT("Role", "As in the role of a modifier key"));
keyRole->SetAlignment(B_ALIGN_RIGHT);
keyRole->SetFont(be_bold_font);
BStringView* keyLabel = new BStringView("key label",
B_TRANSLATE_COMMENT("Key", "As in a computer keyboard key"));
keyLabel->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
keyLabel->SetFont(be_bold_font);
_CreateMenuField(&fCapsMenu, (BMenuField**)&fCapsField, MENU_ITEM_CAPS,
B_TRANSLATE_COMMENT("Caps Lock:", "Caps Lock key role name"));
_CreateMenuField(&fShiftMenu, (BMenuField**)&fShiftField, MENU_ITEM_SHIFT,
B_TRANSLATE_COMMENT("Shift:", "Shift key role name"));
_CreateMenuField(&fControlMenu, (BMenuField**)&fControlField, MENU_ITEM_CONTROL,
B_TRANSLATE_COMMENT("Control:", "Control key role name"));
_CreateMenuField(&fOptionMenu,(BMenuField**) &fOptionField, MENU_ITEM_OPTION,
B_TRANSLATE_COMMENT("Option:", "Option key role name"));
_CreateMenuField(&fCommandMenu, (BMenuField**)&fCommandField, MENU_ITEM_COMMAND,
B_TRANSLATE_COMMENT("Command:", "Command key role name"));
fCancelButton = new BButton("cancelButton", B_TRANSLATE("Cancel"),
new BMessage(B_QUIT_REQUESTED));
fRevertButton = new BButton("revertButton", B_TRANSLATE("Revert"),
new BMessage(kMsgRevertModifiers));
fRevertButton->SetEnabled(false);
fOkButton = new BButton("okButton", B_TRANSLATE("Set modifier keys"),
new BMessage(kMsgApplyModifiers));
fOkButton->MakeDefault(true);
BLayoutBuilder::Group<>(this, B_VERTICAL, B_USE_DEFAULT_SPACING)
.AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING)
.Add(keyRole, 0, 0)
.Add(keyLabel, 1, 0)
.Add(fCapsField->CreateLabelLayoutItem(), 0, 1)
.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 1)
.Add(fCapsField->CreateMenuBarLayoutItem())
.End()
.Add(fShiftField->CreateLabelLayoutItem(), 0, 2)
.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 2)
.Add(fShiftField->CreateMenuBarLayoutItem())
.End()
.Add(fControlField->CreateLabelLayoutItem(), 0, 3)
.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 3)
.Add(fControlField->CreateMenuBarLayoutItem())
.End()
.Add(fOptionField->CreateLabelLayoutItem(), 0, 4)
.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 4)
.Add(fOptionField->CreateMenuBarLayoutItem())
.End()
.Add(fCommandField->CreateLabelLayoutItem(), 0, 5)
.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 5)
.Add(fCommandField->CreateMenuBarLayoutItem())
.End()
.End()
.AddGlue()
.AddGroup(B_HORIZONTAL)
.Add(fRevertButton)
.AddGlue()
.Add(fCancelButton)
.Add(fOkButton)
.End()
.SetInsets(B_USE_WINDOW_SPACING)
.End();
_UpdateStatus();
}
ModifierKeysWindow::~ModifierKeysWindow()
{
be_app->PostMessage(kMsgCloseModifierKeysWindow);
}
void
ModifierKeysWindow::MessageReceived(BMessage* message)
{
switch (message->what) {
case kMsgUpdateModifier:
{
int32 menuitem = MENU_ITEM_FIRST;
int32 key = kDisabled;
for (; menuitem <= MENU_ITEM_LAST; menuitem++) {
if (message->FindInt32(_KeyToString(menuitem), &key) == B_OK)
break;
}
if (key == kDisabled)
return;
uint32 leftKey = _KeyToKeyCode(key);
uint32 rightKey = _KeyToKeyCode(key, true);
switch (menuitem) {
case MENU_ITEM_CAPS:
fCurrentMap->caps_key = leftKey;
break;
case MENU_ITEM_SHIFT:
fCurrentMap->left_shift_key = leftKey;
fCurrentMap->right_shift_key = rightKey;
break;
case MENU_ITEM_CONTROL:
fCurrentMap->left_control_key = leftKey;
fCurrentMap->right_control_key = rightKey;
break;
case MENU_ITEM_OPTION:
fCurrentMap->left_option_key = leftKey;
fCurrentMap->right_option_key = rightKey;
break;
case MENU_ITEM_COMMAND:
fCurrentMap->left_command_key = leftKey;
fCurrentMap->right_command_key = rightKey;
break;
}
_UpdateStatus();
fRevertButton->SetEnabled(memcmp(fCurrentMap, fSavedMap, sizeof(key_map)));
break;
}
case kMsgApplyModifiers:
{
if (_DuplicateKeys() != 0)
break;
BMessage* updateModifiers = new BMessage(kMsgUpdateModifierKeys);
if (fCurrentMap->caps_key != fSavedMap->caps_key)
updateModifiers->AddUInt32("caps_key", fCurrentMap->caps_key);
if (fCurrentMap->left_shift_key != fSavedMap->left_shift_key)
updateModifiers->AddUInt32("left_shift_key", fCurrentMap->left_shift_key);
if (fCurrentMap->right_shift_key != fSavedMap->right_shift_key)
updateModifiers->AddUInt32("right_shift_key", fCurrentMap->right_shift_key);
if (fCurrentMap->left_control_key != fSavedMap->left_control_key)
updateModifiers->AddUInt32("left_control_key", fCurrentMap->left_control_key);
if (fCurrentMap->right_control_key != fSavedMap->right_control_key)
updateModifiers->AddUInt32("right_control_key", fCurrentMap->right_control_key);
if (fCurrentMap->left_option_key != fSavedMap->left_option_key)
updateModifiers->AddUInt32("left_option_key", fCurrentMap->left_option_key);
if (fCurrentMap->right_option_key != fSavedMap->right_option_key)
updateModifiers->AddUInt32("right_option_key", fCurrentMap->right_option_key);
if (fCurrentMap->left_command_key != fSavedMap->left_command_key)
updateModifiers->AddUInt32("left_command_key", fCurrentMap->left_command_key);
if (fCurrentMap->right_command_key != fSavedMap->right_command_key)
updateModifiers->AddUInt32("right_command_key", fCurrentMap->right_command_key);
be_app->PostMessage(updateModifiers);
this->PostMessage(B_QUIT_REQUESTED);
break;
}
case kMsgRevertModifiers:
memcpy(fCurrentMap, fSavedMap, sizeof(key_map));
_UpdateStatus();
fRevertButton->SetEnabled(false);
break;
default:
BWindow::MessageReceived(message);
}
}
void
ModifierKeysWindow::_CreateMenuField(BPopUpMenu** _menu, BMenuField** _menuField, uint32 key,
const char* label)
{
const char* keyName = _KeyToString(key);
const char* name = B_TRANSLATE_NOCOLLECT(keyName);
BPopUpMenu* menu = new BPopUpMenu(name, true, true);
for (int32 key = MENU_ITEM_FIRST; key <= MENU_ITEM_LAST; key++) {
if (key == MENU_ITEM_SEPARATOR) {
BSeparatorItem* separator = new BSeparatorItem;
menu->AddItem(separator, MENU_ITEM_SEPARATOR);
continue;
}
BMessage* message = new BMessage(kMsgUpdateModifier);
message->AddInt32(keyName, key);
StatusMenuItem* item
= new StatusMenuItem(B_TRANSLATE_NOCOLLECT(_KeyToString(key)), message);
menu->AddItem(item, key);
}
menu->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_VERTICAL_UNSET));
BMenuField* menuField = new StatusMenuField(label, menu);
menuField->SetAlignment(B_ALIGN_RIGHT);
*_menu = menu;
*_menuField = menuField;
}
void
ModifierKeysWindow::_MarkMenuItems()
{
_MarkMenuItem("caps lock", fCapsMenu, fCurrentMap->caps_key, fCurrentMap->caps_key);
fShiftField->SetUnmatched(_MarkMenuItem("shift", fShiftMenu,
fCurrentMap->left_shift_key, fCurrentMap->right_shift_key) == false);
fControlField->SetUnmatched(_MarkMenuItem("control", fControlMenu,
fCurrentMap->left_control_key, fCurrentMap->right_control_key) == false);
fOptionField->SetUnmatched(_MarkMenuItem("option", fOptionMenu,
fCurrentMap->left_option_key, fCurrentMap->right_option_key) == false);
fCommandField->SetUnmatched(_MarkMenuItem("command", fCommandMenu,
fCurrentMap->left_command_key, fCurrentMap->right_command_key) == false);
}
bool
ModifierKeysWindow::_MarkMenuItem(const char* role, BPopUpMenu* menu, uint32 left, uint32 right)
{
for (int32 key = MENU_ITEM_FIRST; key <= MENU_ITEM_LAST; key++) {
if (key == MENU_ITEM_SEPARATOR)
continue;
uint32 leftKey = _KeyToKeyCode(key);
uint32 rightKey = _KeyToKeyCode(key, true);
if (leftKey != rightKey && key == MENU_ITEM_CAPS) {
uint32 capsKey = _KeyToKeyCode(MENU_ITEM_CAPS);
if (strcmp(role, "caps lock") == 0 && (left == capsKey || right == capsKey))
menu->ItemAt(key)->SetMarked(true);
else if (left == capsKey)
menu->ItemAt(key)->SetMarked(true);
} else if (left == leftKey && right == rightKey)
menu->ItemAt(key)->SetMarked(true);
}
return menu->FindMarked() != NULL;
}
const char*
ModifierKeysWindow::_KeyToString(int32 key)
{
switch (key) {
case MENU_ITEM_CAPS:
return B_TRANSLATE_COMMENT("Caps Lock key",
"Label of key above Shift, usually Caps Lock");
case MENU_ITEM_SHIFT:
return B_TRANSLATE_COMMENT("Shift key",
"Label of key above Ctrl, usually Shift");
case MENU_ITEM_CONTROL:
return B_TRANSLATE_COMMENT("Ctrl key",
"Label of key farthest from the spacebar, usually Ctrl"
"e.g. Strg for German keyboard");
case MENU_ITEM_OPTION:
return B_TRANSLATE_COMMENT("Win/Cmd key",
"Label of the \"Windows\" key (PC)/Command key (Mac)");
case MENU_ITEM_COMMAND:
return B_TRANSLATE_COMMENT("Alt/Opt key",
"Label of Alt key (PC)/Option key (Mac)");
case MENU_ITEM_DISABLED:
return B_TRANSLATE_COMMENT("Disabled", "Do nothing");
}
return B_EMPTY_STRING;
}
int32
ModifierKeysWindow::_KeyToKeyCode(int32 key, bool right)
{
switch (key) {
case MENU_ITEM_CAPS:
return 0x3b;
case MENU_ITEM_SHIFT:
if (right)
return 0x56;
return 0x4b;
case MENU_ITEM_CONTROL:
if (right)
return 0x60;
return 0x5c;
case MENU_ITEM_OPTION:
if (right)
return 0x67;
return 0x66;
case MENU_ITEM_COMMAND:
if (right)
return 0x5f;
return 0x5d;
case MENU_ITEM_DISABLED:
return kDisabled;
}
return kUnset;
}
void
ModifierKeysWindow::_ValidateDuplicateKeys()
{
uint32 dupMask = _DuplicateKeys();
_ValidateDuplicateKey(fCapsField, CAPS_KEY & dupMask);
_ValidateDuplicateKey(fShiftField, SHIFT_KEY & dupMask);
_ValidateDuplicateKey(fControlField, CONTROL_KEY & dupMask);
_ValidateDuplicateKey(fOptionField, OPTION_KEY & dupMask);
_ValidateDuplicateKey(fCommandField, COMMAND_KEY & dupMask);
fOkButton->SetEnabled(dupMask == 0);
}
void
ModifierKeysWindow::_ValidateDuplicateKey(StatusMenuField* field, uint32 mask)
{
if (mask != 0)
field->SetDuplicate(true);
}
uint32
ModifierKeysWindow::_DuplicateKeys()
{
uint32 duplicateMask = 0;
int32 testLeft, testRight, left, right;
for (int32 testKey = MENU_ITEM_FIRST; testKey < MENU_ITEM_SEPARATOR; testKey++) {
testLeft = kUnset;
testRight = kUnset;
switch (testKey) {
case MENU_ITEM_CAPS:
testLeft = fCurrentMap->caps_key;
testRight = kDisabled;
break;
case MENU_ITEM_SHIFT:
testLeft = fCurrentMap->left_shift_key;
testRight = fCurrentMap->right_shift_key;
break;
case MENU_ITEM_CONTROL:
testLeft = fCurrentMap->left_control_key;
testRight = fCurrentMap->right_control_key;
break;
case MENU_ITEM_OPTION:
testLeft = fCurrentMap->left_option_key;
testRight = fCurrentMap->right_option_key;
break;
case MENU_ITEM_COMMAND:
testLeft = fCurrentMap->left_command_key;
testRight = fCurrentMap->right_command_key;
break;
}
if (testLeft == kUnset && (testRight == kUnset || testRight == kDisabled))
continue;
for (int32 key = MENU_ITEM_FIRST; key < MENU_ITEM_SEPARATOR; key++) {
if (key == testKey) {
continue;
}
left = kUnset;
right = kUnset;
switch (key) {
case MENU_ITEM_CAPS:
left = fCurrentMap->caps_key;
right = kDisabled;
break;
case MENU_ITEM_SHIFT:
left = fCurrentMap->left_shift_key;
right = fCurrentMap->right_shift_key;
break;
case MENU_ITEM_CONTROL:
left = fCurrentMap->left_control_key;
right = fCurrentMap->right_control_key;
break;
case MENU_ITEM_OPTION:
left = fCurrentMap->left_option_key;
right = fCurrentMap->right_option_key;
break;
case MENU_ITEM_COMMAND:
left = fCurrentMap->left_command_key;
right = fCurrentMap->right_command_key;
break;
}
if (left == kUnset && (right == kUnset || right == kDisabled))
continue;
if (left == testLeft || right == testRight) {
duplicateMask |= 1 << testKey;
duplicateMask |= 1 << key;
}
}
}
return duplicateMask;
}
void
ModifierKeysWindow::_UpdateStatus()
{
_MarkMenuItems();
_ValidateDuplicateKeys();
}