Commit 16125bdb authored by Marshall Greenblatt's avatar Marshall Greenblatt

views: Support accelerators in MenuButton label (issue #2102)

parent 6eaf11f0
......@@ -185,6 +185,17 @@ CEF_EXPORT void cef_string_userfree_utf8_free(cef_string_userfree_utf8_t str);
CEF_EXPORT void cef_string_userfree_utf16_free(cef_string_userfree_utf16_t str);
///
// These functions convert utf16 string case using the current ICU locale. This
// may change the length of the string in some cases.
///
CEF_EXPORT int cef_string_utf16_to_lower(const char16* src, size_t src_len,
cef_string_utf16_t* output);
CEF_EXPORT int cef_string_utf16_to_upper(const char16* src, size_t src_len,
cef_string_utf16_t* output);
#ifdef __cplusplus
}
#endif
......
......@@ -7,6 +7,8 @@
#include "libcef/browser/views/menu_button_view.h"
#include "libcef/browser/views/window_impl.h"
#include "ui/gfx/canvas.h"
// static
CefRefPtr<CefMenuButton> CefMenuButton::CreateMenuButton(
CefRefPtr<CefMenuButtonDelegate> delegate,
......@@ -55,6 +57,13 @@ void CefMenuButtonImpl::TriggerMenu() {
root_view()->Activate(nullptr);
}
void CefMenuButtonImpl::SetFocusable(bool focusable) {
CEF_REQUIRE_VALID_RETURN_VOID();
static_cast<CefMenuButtonView*>(root_view())->SetDrawStringsFlags(
focusable ? gfx::Canvas::SHOW_PREFIX : gfx::Canvas::HIDE_PREFIX);
ParentClass::SetFocusable(focusable);
}
CefMenuButtonImpl::CefMenuButtonImpl(CefRefPtr<CefMenuButtonDelegate> delegate)
: ParentClass(delegate) {
DCHECK(delegate);
......
......@@ -40,6 +40,9 @@ class CefMenuButtonImpl :
// CefViewAdapter methods:
std::string GetDebugType() override { return "MenuButton"; }
// CefView methods:
void SetFocusable(bool focusable) override;
private:
// Create a new implementation object.
// Always call Initialize() after creation.
......
......@@ -4,18 +4,31 @@
#include "libcef/browser/views/menu_button_view.h"
#include "ui/gfx/canvas.h"
CefMenuButtonView::CefMenuButtonView(
CefMenuButtonDelegate* cef_delegate)
: ParentClass(cef_delegate) {
DCHECK(cef_delegate);
}
void CefMenuButtonView::Initialize() {
ParentClass::Initialize();
SetDrawStringsFlags(IsFocusable() ? gfx::Canvas::SHOW_PREFIX :
gfx::Canvas::HIDE_PREFIX);
}
CefRefPtr<CefMenuButton> CefMenuButtonView::GetCefMenuButton() const {
CefRefPtr<CefMenuButton> menu_button = GetCefLabelButton()->AsMenuButton();
DCHECK(menu_button);
return menu_button;
}
void CefMenuButtonView::SetDrawStringsFlags(int flags) {
label()->SetDrawStringsFlags(flags);
}
void CefMenuButtonView::OnMenuButtonClicked(views::MenuButton* source,
const gfx::Point& point,
const ui::Event* event) {
......
......@@ -36,10 +36,15 @@ class CefMenuButtonView :
// |cef_delegate| must not be nullptr.
explicit CefMenuButtonView(CefMenuButtonDelegate* cef_delegate);
void Initialize() override;
// Returns the CefMenuButton associated with this view. See comments on
// CefViewView::GetCefView.
CefRefPtr<CefMenuButton> GetCefMenuButton() const;
// Set the flags that control display of accelerator characters.
void SetDrawStringsFlags(int flags);
// views::MenuButtonListener methods:
void OnMenuButtonClicked(views::MenuButton* source,
const gfx::Point& point,
......
......@@ -4,6 +4,7 @@
#include "include/internal/cef_string_types.h"
#include <algorithm>
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
......@@ -275,3 +276,15 @@ CEF_EXPORT void cef_string_userfree_utf16_free(
cef_string_utf16_clear(str);
delete str;
}
CEF_EXPORT int cef_string_utf16_to_lower(const char16* src, size_t src_len,
cef_string_utf16_t* output) {
const base::string16& str = base::i18n::ToLower(base::string16(src, src_len));
return cef_string_utf16_set(str.c_str(), str.length(), output, true);
}
CEF_EXPORT int cef_string_utf16_to_upper(const char16* src, size_t src_len,
cef_string_utf16_t* output) {
const base::string16& str = base::i18n::ToUpper(base::string16(src, src_len));
return cef_string_utf16_set(str.c_str(), str.length(), output, true);
}
......@@ -329,6 +329,7 @@ patches = [
},
{
# Expose callbacks for mouse/keyboard events that trigger menu switching.
# Add accelerator display support to Label.
# https://bitbucket.org/chromiumembedded/cef/issues/2102
'name': 'views_menu_2102',
'path': '../',
......
......@@ -26,6 +26,103 @@ index 0755f27..72db677 100644
// Called when the menu is about to be shown.
virtual void MenuWillShow() {}
diff --git ui/views/controls/label.cc ui/views/controls/label.cc
index 9b04c09..5d99c81 100644
--- ui/views/controls/label.cc
+++ ui/views/controls/label.cc
@@ -28,6 +28,7 @@
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/text_elider.h"
+#include "ui/gfx/text_utils.h"
#include "ui/native_theme/native_theme.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/controls/menu/menu_runner.h"
@@ -36,6 +37,25 @@
#include "ui/views/selection_controller.h"
namespace views {
+
+namespace {
+
+// Strips accelerator character prefixes in |text| if needed, based on |flags|.
+// Returns a range in |text| to underline or Range::InvalidRange() if
+// underlining is not needed.
+gfx::Range StripAcceleratorChars(int flags, base::string16* text) {
+ if (flags & (gfx::Canvas::SHOW_PREFIX | gfx::Canvas::HIDE_PREFIX)) {
+ int char_pos = -1;
+ int char_span = 0;
+ *text = gfx::RemoveAcceleratorChar(*text, '&', &char_pos, &char_span);
+ if ((flags & gfx::Canvas::SHOW_PREFIX) && char_pos != -1)
+ return gfx::Range(char_pos, char_pos + char_span);
+ }
+ return gfx::Range::InvalidRange();
+}
+
+} // namespace
+
// static
const char Label::kViewClassName[] = "Label";
const int Label::kFocusBorderPadding = 1;
@@ -210,6 +230,14 @@ void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
ResetLayout();
}
+void Label::SetDrawStringsFlags(int flags) {
+ if (draw_strings_flags_ == flags)
+ return;
+ is_first_paint_text_ = true;
+ draw_strings_flags_ = flags;
+ ResetLayout();
+}
+
void Label::SetTooltipText(const base::string16& tooltip_text) {
DCHECK(handles_tooltips_);
tooltip_text_ = tooltip_text;
@@ -440,7 +468,19 @@ std::unique_ptr<gfx::RenderText> Label::CreateRenderText(
render_text->SetFontList(font_list());
render_text->set_shadows(shadows());
render_text->SetCursorEnabled(false);
- render_text->SetText(text);
+
+ if (draw_strings_flags_ != 0) {
+ base::string16 text_str = text;
+ gfx::Range range = StripAcceleratorChars(draw_strings_flags_, &text_str);
+ render_text->SetText(text_str);
+ if (range.IsValid()) {
+ render_text->SetDisplayRect(bounds());
+ render_text->ApplyStyle(gfx::UNDERLINE, true, range);
+ }
+ } else {
+ render_text->SetText(text);
+ }
+
return render_text;
}
diff --git ui/views/controls/label.h ui/views/controls/label.h
index 6293cff..d0a5a8f 100644
--- ui/views/controls/label.h
+++ ui/views/controls/label.h
@@ -117,6 +117,10 @@ class VIEWS_EXPORT Label : public View,
void SetElideBehavior(gfx::ElideBehavior elide_behavior);
gfx::ElideBehavior elide_behavior() const { return elide_behavior_; }
+ // Get or set the flags that control display of accelerator characters.
+ void SetDrawStringsFlags(int flags);
+ int draw_strings_flags() const { return draw_strings_flags_; }
+
// Sets the tooltip text. Default behavior for a label (single-line) is to
// show the full text if it is wider than its bounds. Calling this overrides
// the default behavior and lets you set a custom tooltip. To revert to
@@ -333,6 +337,7 @@ class VIEWS_EXPORT Label : public View,
bool collapse_when_hidden_;
int fixed_width_;
int max_width_;
+ int draw_strings_flags_ = 0;
// TODO(ckocagil): Remove is_first_paint_text_ before crbug.com/441028 is
// closed.
diff --git ui/views/controls/menu/menu_controller.cc ui/views/controls/menu/menu_controller.cc
index 79ff77c..a0582c0 100644
--- ui/views/controls/menu/menu_controller.cc
......
......@@ -13,6 +13,32 @@ namespace {
const int kMenuBarGroupId = 100;
// Convert |c| to lowercase using the current ICU locale.
// TODO(jshin): What about Turkish locale? See http://crbug.com/81719.
// If the mnemonic is capital I and the UI language is Turkish, lowercasing it
// results in 'small dotless i', which is different from a 'dotted i'. Similar
// issues may exist for az and lt locales.
base::char16 ToLower(base::char16 c) {
CefStringUTF16 str16;
cef_string_utf16_to_lower(&c, 1, str16.GetWritableStruct());
return str16.length() > 0 ? str16.c_str()[0] : 0;
}
// Extract the mnemonic character from |title|. For example, if |title| is
// "&Test" then the mnemonic character is 'T'.
base::char16 GetMnemonic(const base::string16& title) {
size_t index = 0;
do {
index = title.find('&', index);
if (index != base::string16::npos) {
if (index + 1 != title.size() && title[index + 1] != '&')
return ToLower(title[index + 1]);
index++;
}
} while (index != base::string16::npos);
return 0;
}
} // namespace
ViewsMenuBar::ViewsMenuBar(Delegate* delegate,
......@@ -34,7 +60,7 @@ CefRefPtr<CefPanel> ViewsMenuBar::GetMenuPanel() {
return panel_;
}
CefRefPtr<CefMenuModel> ViewsMenuBar::CreateMenuModel(const std::string& label,
CefRefPtr<CefMenuModel> ViewsMenuBar::CreateMenuModel(const CefString& label,
int* menu_id) {
EnsureMenuPanel();
......@@ -58,7 +84,12 @@ CefRefPtr<CefMenuModel> ViewsMenuBar::CreateMenuModel(const std::string& label,
// Add the new MenuButton to the Planel.
panel_->AddChildView(button);
// Extract the mnemonic that triggers the menu, if any.
base::char16 mnemonic = GetMnemonic(label);
if (mnemonic != 0)
mnemonics_.insert(std::make_pair(mnemonic, new_menu_id));
return model;
}
......@@ -81,9 +112,35 @@ void ViewsMenuBar::SetMenuFocusable(bool focusable) {
}
}
bool ViewsMenuBar::OnKeyEvent(const CefKeyEvent& event) {
if (!panel_)
return false;
if (event.type != KEYEVENT_RAWKEYDOWN)
return false;
// Do not check mnemonics if the Alt or Ctrl modifiers are pressed. For
// example Ctrl+<T> is an accelerator, but <T> only is a mnemonic.
if (event.modifiers & (EVENTFLAG_ALT_DOWN | EVENTFLAG_CONTROL_DOWN))
return false;
MnemonicMap::const_iterator it = mnemonics_.find(ToLower(event.character));
if (it == mnemonics_.end())
return false;
// Set status indicating that we navigated using the keyboard.
last_nav_with_keyboard_ = true;
// Show the selected menu.
TriggerMenuButton(panel_->GetViewForID(it->second));
return true;
}
void ViewsMenuBar::Reset() {
panel_ = NULL;
models_.clear();
mnemonics_.clear();
id_next_ = id_start_;
}
......
......@@ -6,6 +6,7 @@
#define CEF_TESTS_CEFCLIENT_BROWSER_VIEWS_MENU_BAR_H_
#pragma once
#include <map>
#include <string>
#include <vector>
......@@ -50,7 +51,7 @@ class ViewsMenuBar : public CefMenuButtonDelegate,
// Create a new menu with the specified |label|. If |menu_id| is non-NULL it
// will be populated with the new menu ID.
CefRefPtr<CefMenuModel> CreateMenuModel(const std::string& label,
CefRefPtr<CefMenuModel> CreateMenuModel(const CefString& label,
int* menu_id);
// Returns the menu with the specified |menu_id|, or NULL if no such menu
......@@ -63,6 +64,10 @@ class ViewsMenuBar : public CefMenuButtonDelegate,
// when a control not in the menu bar gains focus.
void SetMenuFocusable(bool focusable);
// Key events forwarded from ViewsWindow::OnKeyEvent when the menu bar has
// focus.
bool OnKeyEvent(const CefKeyEvent& event);
// Reset menu bar state.
void Reset();
......@@ -107,6 +112,10 @@ class ViewsMenuBar : public CefMenuButtonDelegate,
std::vector<CefRefPtr<CefMenuModel> > models_;
bool last_nav_with_keyboard_;
// Map of mnemonic to MenuButton ID.
typedef std::map<base::char16, int> MnemonicMap;
MnemonicMap mnemonics_;
IMPLEMENT_REFCOUNTING(ViewsMenuBar);
DISALLOW_COPY_AND_ASSIGN(ViewsMenuBar);
};
......
......@@ -457,6 +457,9 @@ bool ViewsWindow::OnKeyEvent(CefRefPtr<CefWindow> window,
return true;
}
if (menu_has_focus_ && top_menu_bar_)
return top_menu_bar_->OnKeyEvent(event);
return false;
}
......@@ -525,8 +528,8 @@ void ViewsWindow::CreateMenuModel() {
if (top_menu_bar_) {
// Add the menus to the top menu bar.
AddFileMenuItems(top_menu_bar_->CreateMenuModel("File", NULL), 0);
AddTestMenuItems(top_menu_bar_->CreateMenuModel("Tests", NULL));
AddFileMenuItems(top_menu_bar_->CreateMenuModel("&File", NULL), 0);
AddTestMenuItems(top_menu_bar_->CreateMenuModel("&Tests", NULL));
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment