blob: 6e54561d6efd041f1aed1f62c35ae00b66656b52 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_BASE_ACCELERATORS_ACCELERATOR_MAP_H_
#define UI_BASE_ACCELERATORS_ACCELERATOR_MAP_H_
#include <map>
#include <utility>
#include <vector>
#include "base/component_export.h"
#include "build/build_config.h"
#include "ui/base/accelerators/accelerator.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#endif
namespace ui {
// This is a wrapper around an internal std::map of type
// |std::map<Accelerator, V>| where |V| is the mapped value.
//
// Accelerators in Chrome on all platforms are specified with the |key_code|,
// aka VKEY, however certain keys (eg. brackets, comma, period, plus, minus),
// are in different places based on the keyboard layout. In some cases the
// VKEYs don't exist, in some cases they now conflict with other shortcuts.
//
// Chrome OS uses a positional mapping for this subset of keys. Shortcuts
// based on these keys are determined by the position of the key on a US
// keyboard. This was already the case for all non-latin alphabet keyboards
// (Chinese, Japanese, Arabic, Russian, etc.).
//
// To achieve this on Chrome OS for the remaining layouts, an additional
// remapping may happen to the accelerator used for lookup based on the state
// of |use_positional_lookup_|. When false no remapping occurs. When true,
// the |code| aka DomCode (which is by definition fixed position), is used to
// find the US layout VKEY, and that VKEY is used to lookup in the map.
//
// Other non-positional keys, eg. alphanumeric, F-keys, and special keys are
// all not remapped. Alphanumeric keys always continue to follow the
// |code|/VKEY defined by the current layout as is existing behavior.
template <typename V>
class COMPONENT_EXPORT(UI_BASE) AcceleratorMap {
public:
AcceleratorMap() = default;
~AcceleratorMap() = default;
using iterator = typename std::map<Accelerator, V>::iterator;
using const_iterator = typename std::map<Accelerator, V>::const_iterator;
// Lookup an accelerator and return a pointer to the value. If the
// accelerator is not in the map, this returns nullptr.
const V* Find(const Accelerator& accelerator) const {
auto iter = FindImpl(accelerator);
return iter == map_.end() ? nullptr : &iter->second;
}
V* Find(const Accelerator& accelerator) {
// Call the const implementation to avoid duplicating code.
return const_cast<V*>(
const_cast<const AcceleratorMap*>(this)->Find(accelerator));
}
// Lookup an accelerator and return a reference to the value. If the
// accelerator is not present this function will DCHECK.
const V& Get(const Accelerator& accelerator) const {
auto iter = FindImpl(accelerator);
DCHECK(iter != map_.end());
return iter->second;
}
V& Get(const Accelerator& accelerator) {
// Call the const implementation to avoid duplicating code.
return const_cast<V&>(
const_cast<const AcceleratorMap*>(this)->Get(accelerator));
}
V& GetOrInsertDefault(const Accelerator& accelerator) {
#if BUILDFLAG(IS_CHROMEOS)
// Ensure the DomCode is NONE before registering. The DomCode is only
// used during lookup to select the correct VKEY.
if (accelerator.code() != DomCode::NONE) {
Accelerator accelerator_copy = accelerator;
accelerator_copy.reset_code();
return map_[accelerator_copy];
}
#endif
return map_[accelerator];
}
// Erase an accelerator from the map.
bool Erase(const Accelerator& accelerator) {
return map_.erase(accelerator) > 0;
}
// Inserts a new accelerator and value into the map. DCHECKs if the
// accelerator was already in the map.
void InsertNew(const std::pair<const Accelerator, V>& value) {
#if BUILDFLAG(IS_CHROMEOS)
// Ensure the DomCode is NONE before registering. The DomCode is only
// used during lookup to select the correct VKEY.
if (value.first.code() != DomCode::NONE) {
Accelerator accelerator_copy = value.first;
accelerator_copy.reset_code();
auto value_copy = std::make_pair(accelerator_copy, value.second);
auto result = map_.insert(value_copy);
DCHECK(result.second);
return;
}
#endif
auto result = map_.insert(value);
DCHECK(result.second);
}
// Iterators for the internal map.
iterator begin() { return map_.begin(); }
iterator end() { return map_.end(); }
// Returns the number of items in the map.
size_t size() const { return map_.size(); }
// Returns true if the map is empty.
bool empty() const { return map_.empty(); }
#if BUILDFLAG(IS_CHROMEOS)
// When true, lookup operators on the map will remap the |key_code| of
// position-based keys based on the |code|.
void set_use_positional_lookup(bool use_positional_lookup) {
use_positional_lookup_ = use_positional_lookup;
}
#endif
private:
std::map<Accelerator, V> map_;
#if BUILDFLAG(IS_CHROMEOS)
bool use_positional_lookup_ = false;
// For the shortcuts that use positional mapping, the lookup is done based
// on the |key_code| in the US layout. The supplied |code| (DomCode) is used
// to remap the layout specific |key_code| with the US layout equivalent,
// which effectively pins the location of these keys in place regardless of
// the actual key mapping. Note that this only happens for a subset of keys
// and has no overlap with alphanumeric characters or the language equivalent
// keys that map to the alphanumeric set of VKEYS.
//
// One such example is Alt ']'. In the US layout ']' is VKEY_OEM_6, in the
// DE layout it is VKEY_OEM_PLUS. However the key in that position is always
// DomCode::BRACKET_RIGHT regardless of what the key generates when pressed.
// This function uses the DomCode to remap the VKEY back to it's US layout
// equivalent.
//
// See the design doc in crbug.com/1174326 for more information.
Accelerator RemapAcceleratorForLookup(const Accelerator& accelerator) const {
KeyboardCode lookup_key_code = accelerator.key_code();
if (use_positional_lookup_) {
// If there's a valid remapping, use that |KeyboardCode| instead.
KeyboardCode remapped_key_code =
KeycodeConverter::MapPositionalDomCodeToUSShortcutKey(
accelerator.code());
lookup_key_code = remapped_key_code != VKEY_UNKNOWN ? remapped_key_code
: lookup_key_code;
}
return Accelerator(lookup_key_code, DomCode::NONE, accelerator.modifiers(),
accelerator.key_state());
}
#endif // BUILDFLAG(IS_CHROMEOS)
const_iterator FindImpl(const Accelerator& accelerator) const {
#if BUILDFLAG(IS_CHROMEOS)
auto iter = map_.find(RemapAcceleratorForLookup(accelerator));
// Sanity check that a DomCode was never inserted into the map.
DCHECK(iter == map_.end() || iter->first.code() == DomCode::NONE);
return iter;
#endif // BUILDFLAG(IS_CHROMEOS)
return map_.find(accelerator);
}
iterator FindImpl(const Accelerator& accelerator) {
return const_cast<V*>(
const_cast<const AcceleratorMap*>(this)->FindImpl(accelerator));
}
};
} // namespace ui
#endif // UI_BASE_ACCELERATORS_ACCELERATOR_MAP_H_