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