[go: nahoru, domu]

blob: 42a4194977fa99c8e1b003a9ba9865207b827c7a [file] [log] [blame]
// Copyright 2018 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.
#include "ash/shortcut_viewer/views/keyboard_shortcut_item_view.h"
#include <memory>
#include <vector>
#include "ash/shortcut_viewer/keyboard_shortcut_item.h"
#include "ash/shortcut_viewer/keyboard_shortcut_viewer_metadata.h"
#include "ash/shortcut_viewer/strings/grit/shortcut_viewer_strings.h"
#include "ash/shortcut_viewer/vector_icons/vector_icons.h"
#include "ash/shortcut_viewer/views/bubble_view.h"
#include "base/i18n/rtl.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/styled_label.h"
namespace keyboard_shortcut_viewer {
namespace {
// Creates the separator view between bubble views of modifiers and key.
std::unique_ptr<views::View> CreateSeparatorView() {
constexpr SkColor kSeparatorColor = SkColorSetARGB(0xFF, 0x1A, 0x73, 0xE8);
constexpr int kIconSize = 16;
std::unique_ptr<views::ImageView> separator_view =
std::make_unique<views::ImageView>();
separator_view->SetImage(
gfx::CreateVectorIcon(kKsvSeparatorPlusIcon, kSeparatorColor));
separator_view->SetImageSize(gfx::Size(kIconSize, kIconSize));
return separator_view;
}
// Creates the bubble view for modifiers and key.
std::unique_ptr<views::View> CreateBubbleView(const std::u16string& bubble_text,
ui::KeyboardCode key_code) {
auto bubble_view = std::make_unique<BubbleView>();
const gfx::VectorIcon* vector_icon = GetVectorIconForKeyboardCode(key_code);
if (vector_icon)
bubble_view->SetIcon(*vector_icon);
else
bubble_view->SetText(bubble_text);
return bubble_view;
}
} // namespace
KeyboardShortcutItemView::KeyboardShortcutItemView(
const KeyboardShortcutItem& item,
ShortcutCategory category)
: shortcut_item_(&item), category_(category) {
description_label_view_ =
AddChildView(std::make_unique<views::StyledLabel>());
description_label_view_->SetText(
l10n_util::GetStringUTF16(item.description_message_id));
// StyledLabel will flip the alignment if UI layout is right-to-left.
// Flip the alignment here in order to make |description_label_view_| always
// align to left.
description_label_view_->SetHorizontalAlignment(
base::i18n::IsRTL() ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT);
std::vector<size_t> offsets;
std::vector<std::u16string> replacement_strings;
std::vector<std::u16string> accessible_names;
const size_t shortcut_key_codes_size = item.shortcut_key_codes.size();
offsets.reserve(shortcut_key_codes_size);
replacement_strings.reserve(shortcut_key_codes_size);
accessible_names.reserve(shortcut_key_codes_size);
bool has_invalid_dom_key = false;
for (ui::KeyboardCode key_code : item.shortcut_key_codes) {
auto iter = GetKeycodeToString16Cache()->find(key_code);
if (iter == GetKeycodeToString16Cache()->end()) {
iter = GetKeycodeToString16Cache()
->emplace(key_code, GetStringForKeyboardCode(key_code))
.first;
}
const std::u16string& dom_key_string = iter->second;
// If the |key_code| has no mapped |dom_key_string|, we use alternative
// string to indicate that the shortcut is not supported by current keyboard
// layout.
if (dom_key_string.empty()) {
replacement_strings.clear();
accessible_names.clear();
has_invalid_dom_key = true;
break;
}
replacement_strings.push_back(dom_key_string);
std::u16string accessible_name = GetAccessibleNameForKeyboardCode(key_code);
accessible_names.push_back(accessible_name.empty() ? dom_key_string
: accessible_name);
}
int shortcut_message_id;
if (has_invalid_dom_key) {
// |shortcut_message_id| should never be used if the shortcut is not
// supported on the current keyboard layout.
shortcut_message_id = -1;
} else if (item.shortcut_message_id) {
shortcut_message_id = *item.shortcut_message_id;
} else {
// Automatically determine the shortcut message based on the number of
// replacement strings.
// As there are separators inserted between the modifiers, a shortcut with
// N modifiers has 2*N + 1 replacement strings.
switch (replacement_strings.size()) {
case 1:
shortcut_message_id = IDS_KSV_SHORTCUT_ONE_KEY;
break;
case 3:
shortcut_message_id = IDS_KSV_SHORTCUT_ONE_MODIFIER_ONE_KEY;
break;
case 5:
shortcut_message_id = IDS_KSV_SHORTCUT_TWO_MODIFIERS_ONE_KEY;
break;
case 7:
shortcut_message_id = IDS_KSV_SHORTCUT_THREE_MODIFIERS_ONE_KEY;
break;
default:
NOTREACHED() << "Automatically determined shortcut has "
<< replacement_strings.size() << " replacement strings.";
}
}
std::u16string shortcut_string;
std::u16string accessible_string;
if (replacement_strings.empty()) {
shortcut_string = l10n_util::GetStringUTF16(
has_invalid_dom_key ? IDS_KSV_KEY_NO_MAPPING : shortcut_message_id);
accessible_string = shortcut_string;
} else {
shortcut_string = l10n_util::GetStringFUTF16(shortcut_message_id,
replacement_strings, &offsets);
accessible_string = l10n_util::GetStringFUTF16(
shortcut_message_id, accessible_names, /*offsets=*/nullptr);
}
shortcut_label_view_ = AddChildView(std::make_unique<views::StyledLabel>());
shortcut_label_view_->SetText(shortcut_string);
// StyledLabel will flip the alignment if UI layout is right-to-left.
// Flip the alignment here in order to make |shortcut_label_view_| always
// align to right.
shortcut_label_view_->SetHorizontalAlignment(
base::i18n::IsRTL() ? gfx::ALIGN_LEFT : gfx::ALIGN_RIGHT);
DCHECK_EQ(replacement_strings.size(), offsets.size());
// TODO(wutao): make this reliable.
// If the replacement string is "+ ", it indicates to insert a seperator view.
const std::u16string separator_string = base::ASCIIToUTF16("+ ");
for (size_t i = 0; i < offsets.size(); ++i) {
views::StyledLabel::RangeStyleInfo style_info;
style_info.disable_line_wrapping = true;
const std::u16string& replacement_string = replacement_strings[i];
std::unique_ptr<views::View> custom_view =
replacement_string == separator_string
? CreateSeparatorView()
: CreateBubbleView(replacement_string, item.shortcut_key_codes[i]);
style_info.custom_view = custom_view.get();
shortcut_label_view_->AddCustomView(std::move(custom_view));
shortcut_label_view_->AddStyleRange(
gfx::Range(offsets[i], offsets[i] + replacement_strings[i].length()),
style_info);
}
constexpr int kVerticalPadding = 10;
SetBorder(views::CreateEmptyBorder(
gfx::Insets(kVerticalPadding, 0, kVerticalPadding, 0)));
// Use leaf list item role so that name is spoken by screen reader, but
// redundant child label text is not also spoken.
GetViewAccessibility().OverrideRole(ax::mojom::Role::kListItem);
GetViewAccessibility().OverrideIsLeaf(true);
accessible_name_ = description_label_view_->GetText() +
base::ASCIIToUTF16(", ") + accessible_string;
}
void KeyboardShortcutItemView::GetAccessibleNodeData(
ui::AXNodeData* node_data) {
node_data->SetName(accessible_name_);
}
int KeyboardShortcutItemView::GetHeightForWidth(int w) const {
CalculateLayout(w);
return calculated_size_.height();
}
void KeyboardShortcutItemView::Layout() {
CalculateLayout(width());
description_label_view_->SetBoundsRect(description_bounds_);
shortcut_label_view_->SetBoundsRect(shortcut_bounds_);
}
// static
void KeyboardShortcutItemView::ClearKeycodeToString16Cache() {
GetKeycodeToString16Cache()->clear();
}
// static
std::map<ui::KeyboardCode, std::u16string>*
KeyboardShortcutItemView::GetKeycodeToString16Cache() {
static base::NoDestructor<std::map<ui::KeyboardCode, std::u16string>>
key_code_to_string16_cache;
return key_code_to_string16_cache.get();
}
void KeyboardShortcutItemView::CalculateLayout(int width) const {
if (width == calculated_size_.width())
return;
TRACE_EVENT0("shortcut_viewer", "CalculateLayout");
const gfx::Insets insets = GetInsets();
const int content_width = std::max(width - insets.width(), 0);
// The max width of |shortcut_label_view_| as a ratio of its parent view's
// width. This value is chosen to put all the bubble views in one line.
constexpr float kShortcutViewPreferredWidthRatio = 0.69f;
// The minimum spacing between |description_label_view_| and
// |shortcut_label_view_|.
constexpr int kMinimumSpacing = 64;
const int shortcut_width = content_width * kShortcutViewPreferredWidthRatio;
const auto& shortcut_size_info =
shortcut_label_view_->GetLayoutSizeInfoForWidth(shortcut_width);
const int shortcut_height = shortcut_size_info.total_size.height();
const auto top_line_height = [](const auto& size_info) {
// When nothing fits, it doesn't really matter what we do; using the overall
// height (which is the height of the label insets) is sane.
return size_info.line_sizes.empty() ? size_info.total_size.height()
: size_info.line_sizes[0].height();
};
const int shortcut_top_line_center_y =
shortcut_label_view_->GetInsets().top() +
(top_line_height(shortcut_size_info) / 2);
// The width of |description_label_view_| will be dynamically adjusted to fill
// the spacing.
int description_width =
content_width - shortcut_size_info.total_size.width() - kMinimumSpacing;
if (description_width < kMinimumSpacing) {
// The min width of |description_label_view_| as a ratio of its parent
// view's width when the |description_view_width| calculated above is
// smaller than |kMinimumSpacing|.
constexpr float kDescriptionViewMinWidthRatio = 0.29f;
description_width = content_width * kDescriptionViewMinWidthRatio;
}
const auto& description_size_info =
description_label_view_->GetLayoutSizeInfoForWidth(description_width);
const int description_height = description_size_info.total_size.height();
const int description_top_line_center_y =
description_label_view_->GetInsets().top() +
(top_line_height(description_size_info) / 2);
// |shortcut_label_view_| could have bubble view in the top line, whose
// height is larger than normal text in |description_label_view_|. Otherwise,
// the top line height in the two views should be equal.
DCHECK_GE(shortcut_top_line_center_y, description_top_line_center_y);
// Align the vertical center of the top lines of both views.
const int description_delta_y =
shortcut_top_line_center_y - description_top_line_center_y;
// Left align the |description_label_view_|.
description_bounds_ =
gfx::Rect(insets.left(), insets.top() + description_delta_y,
description_width, description_height);
// Right align the |shortcut_label_view_|.
shortcut_bounds_ = gfx::Rect(insets.left() + content_width - shortcut_width,
insets.top(), shortcut_width, shortcut_height);
// Add 2 * |description_delta_y| to balance the top and bottom paddings.
const int content_height =
std::max(shortcut_height, description_height + 2 * description_delta_y);
calculated_size_ = gfx::Size(width, content_height + insets.height());
}
} // namespace keyboard_shortcut_viewer