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 26 Accelerator::Accelerator() 27 : key_code_(ui::VKEY_UNKNOWN), 28 type_(ui::ET_KEY_PRESSED), 29 modifiers_(0) { 30 } 31 32 Accelerator::Accelerator(KeyboardCode keycode, int modifiers) 33 : key_code_(keycode), 34 type_(ui::ET_KEY_PRESSED), 35 modifiers_(modifiers) { 36 } 37 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 46 Accelerator::~Accelerator() { 47 } 48 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 62 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 70 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 81 bool Accelerator::operator !=(const Accelerator& rhs) const { 82 return !(*this == rhs); 83 } 84 85 bool Accelerator::IsShiftDown() const { 86 return (modifiers_ & EF_SHIFT_DOWN) != 0; 87 } 88 89 bool Accelerator::IsCtrlDown() const { 90 return (modifiers_ & EF_CONTROL_DOWN) != 0; 91 } 92 93 bool Accelerator::IsAltDown() const { 94 return (modifiers_ & EF_ALT_DOWN) != 0; 95 } 96 97 bool Accelerator::IsCmdDown() const { 98 return (modifiers_ & EF_COMMAND_DOWN) != 0; 99 } 100 101 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