1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/base/accelerators/accelerator.h"
6
7#if defined(OS_WIN)
8#include <windows.h>
9#endif
10
11#include "base/i18n/rtl.h"
12#include "base/logging.h"
13#include "base/strings/string_util.h"
14#include "base/strings/utf_string_conversions.h"
15#include "ui/base/l10n/l10n_util.h"
16#include "ui/strings/grit/ui_strings.h"
17
18#if !defined(OS_WIN) && (defined(USE_AURA) || defined(OS_MACOSX))
19#include "ui/events/keycodes/keyboard_code_conversion.h"
20#endif
21
22namespace ui {
23
24Accelerator::Accelerator()
25    : key_code_(ui::VKEY_UNKNOWN),
26      type_(ui::ET_KEY_PRESSED),
27      modifiers_(0),
28      is_repeat_(false) {
29}
30
31Accelerator::Accelerator(KeyboardCode keycode, int modifiers)
32    : key_code_(keycode),
33      type_(ui::ET_KEY_PRESSED),
34      modifiers_(modifiers),
35      is_repeat_(false) {
36}
37
38Accelerator::Accelerator(const Accelerator& accelerator) {
39  key_code_ = accelerator.key_code_;
40  type_ = accelerator.type_;
41  modifiers_ = accelerator.modifiers_;
42  is_repeat_ = accelerator.is_repeat_;
43  if (accelerator.platform_accelerator_.get())
44    platform_accelerator_ = accelerator.platform_accelerator_->CreateCopy();
45}
46
47Accelerator::~Accelerator() {
48}
49
50Accelerator& Accelerator::operator=(const Accelerator& accelerator) {
51  if (this != &accelerator) {
52    key_code_ = accelerator.key_code_;
53    type_ = accelerator.type_;
54    modifiers_ = accelerator.modifiers_;
55    is_repeat_ = accelerator.is_repeat_;
56    if (accelerator.platform_accelerator_.get())
57      platform_accelerator_ = accelerator.platform_accelerator_->CreateCopy();
58    else
59      platform_accelerator_.reset();
60  }
61  return *this;
62}
63
64bool Accelerator::operator <(const Accelerator& rhs) const {
65  if (key_code_ != rhs.key_code_)
66    return key_code_ < rhs.key_code_;
67  if (type_ != rhs.type_)
68    return type_ < rhs.type_;
69  return modifiers_ < rhs.modifiers_;
70}
71
72bool Accelerator::operator ==(const Accelerator& rhs) const {
73  if ((key_code_ == rhs.key_code_) && (type_ == rhs.type_) &&
74      (modifiers_ == rhs.modifiers_))
75    return true;
76
77  bool platform_equal =
78      platform_accelerator_.get() && rhs.platform_accelerator_.get() &&
79      platform_accelerator_.get() == rhs.platform_accelerator_.get();
80
81  return platform_equal;
82}
83
84bool Accelerator::operator !=(const Accelerator& rhs) const {
85  return !(*this == rhs);
86}
87
88bool Accelerator::IsShiftDown() const {
89  return (modifiers_ & EF_SHIFT_DOWN) != 0;
90}
91
92bool Accelerator::IsCtrlDown() const {
93  return (modifiers_ & EF_CONTROL_DOWN) != 0;
94}
95
96bool Accelerator::IsAltDown() const {
97  return (modifiers_ & EF_ALT_DOWN) != 0;
98}
99
100bool Accelerator::IsCmdDown() const {
101  return (modifiers_ & EF_COMMAND_DOWN) != 0;
102}
103
104bool Accelerator::IsRepeat() const {
105  return is_repeat_;
106}
107
108base::string16 Accelerator::GetShortcutText() const {
109  int string_id = 0;
110  switch (key_code_) {
111    case ui::VKEY_TAB:
112      string_id = IDS_APP_TAB_KEY;
113      break;
114    case ui::VKEY_RETURN:
115      string_id = IDS_APP_ENTER_KEY;
116      break;
117    case ui::VKEY_ESCAPE:
118      string_id = IDS_APP_ESC_KEY;
119      break;
120    case ui::VKEY_PRIOR:
121      string_id = IDS_APP_PAGEUP_KEY;
122      break;
123    case ui::VKEY_NEXT:
124      string_id = IDS_APP_PAGEDOWN_KEY;
125      break;
126    case ui::VKEY_END:
127      string_id = IDS_APP_END_KEY;
128      break;
129    case ui::VKEY_HOME:
130      string_id = IDS_APP_HOME_KEY;
131      break;
132    case ui::VKEY_INSERT:
133      string_id = IDS_APP_INSERT_KEY;
134      break;
135    case ui::VKEY_DELETE:
136      string_id = IDS_APP_DELETE_KEY;
137      break;
138    case ui::VKEY_LEFT:
139      string_id = IDS_APP_LEFT_ARROW_KEY;
140      break;
141    case ui::VKEY_RIGHT:
142      string_id = IDS_APP_RIGHT_ARROW_KEY;
143      break;
144    case ui::VKEY_UP:
145      string_id = IDS_APP_UP_ARROW_KEY;
146      break;
147    case ui::VKEY_DOWN:
148      string_id = IDS_APP_DOWN_ARROW_KEY;
149      break;
150    case ui::VKEY_BACK:
151      string_id = IDS_APP_BACKSPACE_KEY;
152      break;
153    case ui::VKEY_F1:
154      string_id = IDS_APP_F1_KEY;
155      break;
156    case ui::VKEY_F11:
157      string_id = IDS_APP_F11_KEY;
158      break;
159    case ui::VKEY_OEM_COMMA:
160      string_id = IDS_APP_COMMA_KEY;
161      break;
162    case ui::VKEY_OEM_PERIOD:
163      string_id = IDS_APP_PERIOD_KEY;
164      break;
165    case ui::VKEY_MEDIA_NEXT_TRACK:
166      string_id = IDS_APP_MEDIA_NEXT_TRACK_KEY;
167      break;
168    case ui::VKEY_MEDIA_PLAY_PAUSE:
169      string_id = IDS_APP_MEDIA_PLAY_PAUSE_KEY;
170      break;
171    case ui::VKEY_MEDIA_PREV_TRACK:
172      string_id = IDS_APP_MEDIA_PREV_TRACK_KEY;
173      break;
174    case ui::VKEY_MEDIA_STOP:
175      string_id = IDS_APP_MEDIA_STOP_KEY;
176      break;
177    default:
178      break;
179  }
180
181  base::string16 shortcut;
182  if (!string_id) {
183#if defined(OS_WIN)
184    // Our fallback is to try translate the key code to a regular character
185    // unless it is one of digits (VK_0 to VK_9). Some keyboard
186    // layouts have characters other than digits assigned in
187    // an unshifted mode (e.g. French AZERY layout has 'a with grave
188    // accent' for '0'). For display in the menu (e.g. Ctrl-0 for the
189    // default zoom level), we leave VK_[0-9] alone without translation.
190    wchar_t key;
191    if (key_code_ >= '0' && key_code_ <= '9')
192      key = key_code_;
193    else
194      key = LOWORD(::MapVirtualKeyW(key_code_, MAPVK_VK_TO_CHAR));
195    shortcut += key;
196#elif defined(USE_AURA) || defined(OS_MACOSX)
197    const uint16 c = GetCharacterFromKeyCode(key_code_, false);
198    if (c != 0)
199      shortcut +=
200          static_cast<base::string16::value_type>(base::ToUpperASCII(c));
201#endif
202  } else {
203    shortcut = l10n_util::GetStringUTF16(string_id);
204  }
205
206  // Checking whether the character used for the accelerator is alphanumeric.
207  // If it is not, then we need to adjust the string later on if the locale is
208  // right-to-left. See below for more information of why such adjustment is
209  // required.
210  base::string16 shortcut_rtl;
211  bool adjust_shortcut_for_rtl = false;
212  if (base::i18n::IsRTL() && shortcut.length() == 1 &&
213      !IsAsciiAlpha(shortcut[0]) && !IsAsciiDigit(shortcut[0])) {
214    adjust_shortcut_for_rtl = true;
215    shortcut_rtl.assign(shortcut);
216  }
217
218  if (IsShiftDown())
219    shortcut = l10n_util::GetStringFUTF16(IDS_APP_SHIFT_MODIFIER, shortcut);
220
221  // Note that we use 'else-if' in order to avoid using Ctrl+Alt as a shortcut.
222  // See http://blogs.msdn.com/oldnewthing/archive/2004/03/29/101121.aspx for
223  // more information.
224  if (IsCtrlDown())
225    shortcut = l10n_util::GetStringFUTF16(IDS_APP_CONTROL_MODIFIER, shortcut);
226  else if (IsAltDown())
227    shortcut = l10n_util::GetStringFUTF16(IDS_APP_ALT_MODIFIER, shortcut);
228
229  if (IsCmdDown())
230    shortcut = l10n_util::GetStringFUTF16(IDS_APP_COMMAND_MODIFIER, shortcut);
231
232  // For some reason, menus in Windows ignore standard Unicode directionality
233  // marks (such as LRE, PDF, etc.). On RTL locales, we use RTL menus and
234  // therefore any text we draw for the menu items is drawn in an RTL context.
235  // Thus, the text "Ctrl++" (which we currently use for the Zoom In option)
236  // appears as "++Ctrl" in RTL because the Unicode BiDi algorithm puts
237  // punctuations on the left when the context is right-to-left. Shortcuts that
238  // do not end with a punctuation mark (such as "Ctrl+H" do not have this
239  // problem).
240  //
241  // The only way to solve this problem is to adjust the string if the locale
242  // is RTL so that it is drawn correctly in an RTL context. Instead of
243  // returning "Ctrl++" in the above example, we return "++Ctrl". This will
244  // cause the text to appear as "Ctrl++" when Windows draws the string in an
245  // RTL context because the punctuation no longer appears at the end of the
246  // string.
247  //
248  // TODO(idana) bug# 1232732: this hack can be avoided if instead of using
249  // views::Menu we use views::MenuItemView because the latter is a View
250  // subclass and therefore it supports marking text as RTL or LTR using
251  // standard Unicode directionality marks.
252  if (adjust_shortcut_for_rtl) {
253    int key_length = static_cast<int>(shortcut_rtl.length());
254    DCHECK_GT(key_length, 0);
255    shortcut_rtl.append(base::ASCIIToUTF16("+"));
256
257    // Subtracting the size of the shortcut key and 1 for the '+' sign.
258    shortcut_rtl.append(shortcut, 0, shortcut.length() - key_length - 1);
259    shortcut.swap(shortcut_rtl);
260  }
261
262  return shortcut;
263}
264
265}  // namespace ui
266