910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/base/accelerators/accelerator.h"

#include <stdint.h>

#include <tuple>

#include "base/check_op.h"
#include "base/i18n/rtl.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/cxx23_to_underlying.h"
#include "build/build_config.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/strings/grit/ui_strings.h"

#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif

#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif

#if !BUILDFLAG(IS_WIN) && (defined(USE_AURA) || BUILDFLAG(IS_MAC))
#include "ui/events/keycodes/keyboard_code_conversion.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "ui/base/accelerators/ash/quick_insert_event_property.h"
#include "ui/base/ui_base_features.h"
#endif

#if BUILDFLAG(USE_BLINK)
#include "ui/base/accelerators/media_keys_listener.h"
#endif

namespace ui {

Accelerator::Accelerator(const KeyEvent& key_event)
    : key_code_(key_event.key_code()),
      key_state_(key_event.type() == EventType::kKeyPressed
                     ? KeyState::PRESSED
                     : KeyState::RELEASED),
      // |modifiers_| may include the repeat flag.
      modifiers_(key_event.flags() & kInterestingFlagsMask),
      time_stamp_(key_event.time_stamp()),
      interrupted_by_mouse_event_(false),
      source_device_id_(key_event.source_device_id()) {
#if BUILDFLAG(IS_CHROMEOS)
  if (features::IsImprovedKeyboardShortcutsEnabled()) {
    code_ = key_event.code();
  }

  // Rewrite to Quick Insert based on the presence of the property.
  if (key_event.key_code() == VKEY_ASSISTANT &&
      HasQuickInsertProperty(key_event)) {
    key_code_ = VKEY_QUICK_INSERT;
  }
#endif
}

KeyEvent Accelerator::ToKeyEvent() const {
  return KeyEvent(key_state() == Accelerator::KeyState::PRESSED
                      ? EventType::kKeyPressed
                      : EventType::kKeyReleased,
                  key_code(),
#if BUILDFLAG(IS_CHROMEOS)
                  code(),
#endif
                  modifiers(), time_stamp());
}

#if BUILDFLAG(USE_BLINK)
bool Accelerator::IsMediaKey() const {
  if (modifiers_ != EF_NONE) {
    return false;
  }

  return ui::MediaKeysListener::IsMediaKeycode(key_code_);
}
#endif

std::vector<std::u16string> Accelerator::GetShortcutVectorRepresentation()
    const {
  std::vector<std::u16string> shortcut_vector;
  if (IsEmpty()) {
    return shortcut_vector;
  }

  std::u16string key_code = GetKeyCodeStringForShortcut();

#if BUILDFLAG(IS_MAC)
  shortcut_vector = GetShortFormModifiers();
  shortcut_vector.push_back(key_code);
#else
  std::vector<std::u16string> modifiers = GetLongFormModifiers();
  // For some reason, menus in Windows ignore standard Unicode directionality
  // marks (such as LRE, PDF, etc.). On RTL locales, we use RTL menus and
  // therefore any text we draw for the menu items is drawn in an RTL context.
  // Thus, the text "Ctrl++" (which we currently use for the Zoom In option)
  // appears as "++Ctrl" in RTL because the Unicode BiDi algorithm puts
  // punctuation on the left when the context is right-to-left. Shortcuts that
  // do not end with a punctuation mark (such as "Ctrl+H" do not have this
  // problem).
  //
  // The only way to solve this problem is to adjust the shortcut representation
  // if the locale is RTL so that it is drawn correctly in an RTL context.
  // Instead of returning "Ctrl++" in the above example, we return "++Ctrl".
  // This will cause the text to appear as "Ctrl++" when Windows draws the
  // string in an RTL context because the punctuation no longer appears at the
  // end of the string.
  // To accomplish this, if the character used for the accelerator is not
  // alphanumeric and the locale is RTL place the character key code before the
  // modifiers. Otherwise, place it after the modifiers as per usual.
  //
  // TODO(crbug.com/40175605): This hack of doing the RTL adjustment here was
  // intended to be removed when the menu system moved to MenuItemView. That was
  // crbug.com/2822, closed in 2010. Can we finally remove all of this?
  if (base::i18n::IsRTL() && key_code.length() == 1 &&
      !base::IsAsciiAlphaNumeric(key_code[0])) {
    shortcut_vector.push_back(key_code);
    shortcut_vector.insert(shortcut_vector.end(), modifiers.begin(),
                           modifiers.end());
  } else {
    shortcut_vector.insert(shortcut_vector.end(), modifiers.begin(),
                           modifiers.end());
    shortcut_vector.push_back(key_code);
  }

#endif  // BUILDFLAG(IS_MAC)

  return shortcut_vector;
}

std::u16string Accelerator::GetShortcutText() const {
  std::u16string shortcut;
  std::vector<std::u16string> shortcut_vector =
      GetShortcutVectorRepresentation();

  // Shortcut text is expected to be represented using '+' as a separator on all
  // platforms except Mac, where no separator is used.
#if BUILDFLAG(IS_MAC)
  shortcut = base::JoinString(shortcut_vector, u"");
#else
  shortcut = base::JoinString(shortcut_vector, u"+");
#endif

  return shortcut;
}

std::u16string Accelerator::GetKeyCodeStringForShortcut() const {
  std::u16string key_string;
#if BUILDFLAG(IS_MAC)
  key_string = KeyCodeToMacSymbol();
#else
  key_string = KeyCodeToName();
#endif

  if (key_string.empty()) {
#if BUILDFLAG(IS_WIN)
    // Our fallback is to try translate the key code to a regular character
    // unless it is one of digits (VK_0 to VK_9). Some keyboard
    // layouts have characters other than digits assigned in
    // an unshifted mode (e.g. French AZERY layout has 'a with grave
    // accent' for '0'). For display in the menu (e.g. Ctrl-0 for the
    // default zoom level), we leave VK_[0-9] alone without translation.
    wchar_t key;
    if (base::IsAsciiDigit(base::to_underlying(key_code_))) {
      key = static_cast<wchar_t>(key_code_);
    } else {
      key = LOWORD(::MapVirtualKeyW(key_code_, MAPVK_VK_TO_CHAR));
    }
    // If there is no translation for the given |key_code_| (e.g.
    // VKEY_UNKNOWN), |::MapVirtualKeyW| returns 0.
    if (key != 0) {
      key_string += key;
    }
#elif defined(USE_AURA) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
    const uint16_t c = DomCodeToUsLayoutCharacter(
        UsLayoutKeyboardCodeToDomCode(key_code_), false);
    if (c != 0) {
      key_string +=
          static_cast<std::u16string::value_type>(base::ToUpperASCII(c));
    }
#endif
  }

  return key_string;
}

#if BUILDFLAG(IS_MAC)
// In macOS 10.13, the glyphs used for page up, page down, home, and end were
// changed from the arrows below to new, skinny arrows. The tricky bit is that
// the underlying Unicode characters weren't changed, just the font used. Maybe
// the keyboard font, CTFontCreateUIFontForLanguage, with key
// kCTFontUIFontMenuItemCmdKey, can be used everywhere this symbol is used. (If
// so, then the RTL stuff will need to be removed.)
std::u16string Accelerator::KeyCodeToMacSymbol() const {
  switch (key_code_) {
    case VKEY_CAPITAL:
      return u"⇪";  // U+21EA, UPWARDS WHITE ARROW FROM BAR
    case VKEY_RETURN:
      return u"⌤";  // U+2324, UP ARROWHEAD BETWEEN TWO HORIZONTAL BARS
    case VKEY_BACK:
      return u"⌫";  // U+232B, ERASE TO THE LEFT
    case VKEY_ESCAPE:
      return u"⎋";  // U+238B, BROKEN CIRCLE WITH NORTHWEST ARROW
    case VKEY_RIGHT:
      return u"→";  // U+2192, RIGHTWARDS ARROW
    case VKEY_LEFT:
      return u"←";  // U+2190, LEFTWARDS ARROW
    case VKEY_UP:
      return u"↑";  // U+2191, UPWARDS ARROW
    case VKEY_DOWN:
      return u"↓";  // U+2193, DOWNWARDS ARROW
    case VKEY_PRIOR:
      return u"⇞";  // U+21DE, UPWARDS ARROW WITH DOUBLE STROKE
    case VKEY_NEXT:
      return u"⇟";  // U+21DF, DOWNWARDS ARROW WITH DOUBLE STROKE
    case VKEY_HOME:
      return base::i18n::IsRTL() ? u"↗"   // U+2197, NORTH EAST ARROW
                                 : u"↖";  // U+2196, NORTH WEST ARROW
    case VKEY_END:
      return base::i18n::IsRTL() ? u"↙"   // U+2199, SOUTH WEST ARROW
                                 : u"↘";  // U+2198, SOUTH EAST ARROW
    case VKEY_TAB:
      return u"⇥";  // U+21E5, RIGHTWARDS ARROW TO BAR
    // Mac has a shift-tab icon ("⇤", U+21E4, LEFTWARDS ARROW TO BAR) but we
    // don't use it. "Space" and some other keys are written out; fall back to
    // KeyCodeToName() for those (and any other unhandled keys).
    default:
      return KeyCodeToName();
  }
}
#endif  // BUILDFLAG(IS_MAC)

std::u16string Accelerator::KeyCodeToName() const {
  int string_id = 0;
  switch (key_code_) {
    case VKEY_TAB:
      string_id = IDS_APP_TAB_KEY;
      break;
    case VKEY_RETURN:
      string_id = IDS_APP_ENTER_KEY;
      break;
    case VKEY_SPACE:
      string_id = IDS_APP_SPACE_KEY;
      break;
    case VKEY_PRIOR:
      string_id = IDS_APP_PAGEUP_KEY;
      break;
    case VKEY_NEXT:
      string_id = IDS_APP_PAGEDOWN_KEY;
      break;
    case VKEY_END:
      string_id = IDS_APP_END_KEY;
      break;
    case VKEY_HOME:
      string_id = IDS_APP_HOME_KEY;
      break;
    case VKEY_INSERT:
      string_id = IDS_APP_INSERT_KEY;
      break;
    case VKEY_DELETE:
      string_id = IDS_APP_DELETE_KEY;
      break;
    case VKEY_LEFT:
      string_id = IDS_APP_LEFT_ARROW_KEY;
      break;
    case VKEY_RIGHT:
      string_id = IDS_APP_RIGHT_ARROW_KEY;
      break;
    case VKEY_UP:
      string_id = IDS_APP_UP_ARROW_KEY;
      break;
    case VKEY_DOWN:
      string_id = IDS_APP_DOWN_ARROW_KEY;
      break;
    case VKEY_ESCAPE:
      string_id = IDS_APP_ESC_KEY;
      break;
    case VKEY_BACK:
      string_id = IDS_APP_BACKSPACE_KEY;
      break;
    case VKEY_F1:
      string_id = IDS_APP_F1_KEY;
      break;
    case VKEY_F6:
      string_id = IDS_APP_F6_KEY;
      break;
    case VKEY_F11:
      string_id = IDS_APP_F11_KEY;
      break;
#if !BUILDFLAG(IS_MAC)
    // On Mac, commas and periods are used literally in accelerator text.
    case VKEY_OEM_COMMA:
      string_id = IDS_APP_COMMA_KEY;
      break;
    case VKEY_OEM_PERIOD:
      string_id = IDS_APP_PERIOD_KEY;
      break;
#endif
    case VKEY_MEDIA_NEXT_TRACK:
      string_id = IDS_APP_MEDIA_NEXT_TRACK_KEY;
      break;
    case VKEY_MEDIA_PLAY_PAUSE:
      string_id = IDS_APP_MEDIA_PLAY_PAUSE_KEY;
      break;
    case VKEY_MEDIA_PREV_TRACK:
      string_id = IDS_APP_MEDIA_PREV_TRACK_KEY;
      break;
    case VKEY_MEDIA_STOP:
      string_id = IDS_APP_MEDIA_STOP_KEY;
      break;
    default:
      break;
  }
  return string_id ? l10n_util::GetStringUTF16(string_id) : std::u16string();
}

std::vector<std::u16string> Accelerator::GetLongFormModifiers() const {
  std::vector<std::u16string> modifiers;

  if (IsCmdDown()) {
#if BUILDFLAG(IS_MAC)
    modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_COMMAND_KEY));
#elif BUILDFLAG(IS_CHROMEOS)
    modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_SEARCH_KEY));
#elif BUILDFLAG(IS_WIN)
    modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_WINDOWS_KEY));
#elif BUILDFLAG(IS_LINUX)
    modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_SUPER_KEY));
#else
    NOTREACHED();
#endif
  }

  if (IsAltDown()) {
    modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_ALT_KEY));
  }

  if (IsCtrlDown()) {
    modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_CTRL_KEY));
  }

  if (IsShiftDown()) {
    modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_SHIFT_KEY));
  }

  return modifiers;
}

std::vector<std::u16string> Accelerator::GetShortFormModifiers() const {
  std::vector<std::u16string> modifiers;
  // Add modifiers in the order that matches how they are displayed in native
  // menus.
  if (IsCtrlDown()) {
    modifiers.push_back(u"⌃");  // U+2303, UP ARROWHEAD
  }
  if (IsAltDown()) {
    modifiers.push_back(u"⌥");  // U+2325, OPTION KEY
  }
  if (IsShiftDown()) {
    modifiers.push_back(u"⇧");  // U+21E7, UPWARDS WHITE ARROW
  }
  if (IsCmdDown()) {
    modifiers.push_back(u"⌘");  // U+2318, PLACE OF INTEREST SIGN
  }

  if (IsFunctionDown()) {
    // The real "fn" used by menus is actually U+E23E in the Private Use Area in
    // the keyboard font obtained with CTFontCreateUIFontForLanguage, with key
    // kCTFontUIFontMenuItemCmdKey. Because this function must return a raw
    // Unicode string with no specified font, return a string of characters.
    //
    // Newer Mac keyboards have a globe symbol on the fn key that is used in
    // menus instead of "fn". That globe symbol is actually U+1F310 + U+FE0E,
    // the emoji globe + the variation selector that indicates the text-style
    // presentation. (🌐︎)
    //
    // Whether or not "fn" or the globe is displayed as the menu shortcut
    // modifier depends on whether there is an attached keyboard with a globe
    // symbol on it. Rather than rummaging around in the IORegistry, where the
    // HID driver for the keyboard has a SupportsGlobeKey = True property, it's
    // probably best to just make a call to the HIServices function
    // HIS_XPC_GetGlobeKeyAvailability() and let it do the magic. See AppKit's
    // -[NSKeyboardShortcut localizedModifierMaskDisplayName] for an example of
    // this.
    //
    // TODO(http://crbug.com/40800376): Implement all of this when text-style
    // presentations are implemented for Views in https://crbug.com/40137571.
    modifiers.push_back(u"(fn) ");
  }

  return modifiers;
}

}  // namespace ui