1 // Copyright (c) 2009 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 #import "chrome/browser/ui/cocoa/rwhvm_editcommand_helper.h" 6 7 #import <objc/runtime.h> 8 9 #import "chrome/browser/renderer_host/render_widget_host_view_mac.h" 10 #include "content/browser/renderer_host/render_widget_host.h" 11 12 namespace { 13 // The names of all the objc selectors w/o ':'s added to an object by 14 // AddEditingSelectorsToClass(). 15 // 16 // This needs to be kept in Sync with WEB_COMMAND list in the WebKit tree at: 17 // WebKit/mac/WebView/WebHTMLView.mm . 18 const char* kEditCommands[] = { 19 "alignCenter", 20 "alignJustified", 21 "alignLeft", 22 "alignRight", 23 "copy", 24 "cut", 25 "delete", 26 "deleteBackward", 27 "deleteBackwardByDecomposingPreviousCharacter", 28 "deleteForward", 29 "deleteToBeginningOfLine", 30 "deleteToBeginningOfParagraph", 31 "deleteToEndOfLine", 32 "deleteToEndOfParagraph", 33 "deleteToMark", 34 "deleteWordBackward", 35 "deleteWordForward", 36 "ignoreSpelling", 37 "indent", 38 "insertBacktab", 39 "insertLineBreak", 40 "insertNewline", 41 "insertNewlineIgnoringFieldEditor", 42 "insertParagraphSeparator", 43 "insertTab", 44 "insertTabIgnoringFieldEditor", 45 "makeTextWritingDirectionLeftToRight", 46 "makeTextWritingDirectionNatural", 47 "makeTextWritingDirectionRightToLeft", 48 "moveBackward", 49 "moveBackwardAndModifySelection", 50 "moveDown", 51 "moveDownAndModifySelection", 52 "moveForward", 53 "moveForwardAndModifySelection", 54 "moveLeft", 55 "moveLeftAndModifySelection", 56 "moveParagraphBackwardAndModifySelection", 57 "moveParagraphForwardAndModifySelection", 58 "moveRight", 59 "moveRightAndModifySelection", 60 "moveToBeginningOfDocument", 61 "moveToBeginningOfDocumentAndModifySelection", 62 "moveToBeginningOfLine", 63 "moveToBeginningOfLineAndModifySelection", 64 "moveToBeginningOfParagraph", 65 "moveToBeginningOfParagraphAndModifySelection", 66 "moveToBeginningOfSentence", 67 "moveToBeginningOfSentenceAndModifySelection", 68 "moveToEndOfDocument", 69 "moveToEndOfDocumentAndModifySelection", 70 "moveToEndOfLine", 71 "moveToEndOfLineAndModifySelection", 72 "moveToEndOfParagraph", 73 "moveToEndOfParagraphAndModifySelection", 74 "moveToEndOfSentence", 75 "moveToEndOfSentenceAndModifySelection", 76 "moveUp", 77 "moveUpAndModifySelection", 78 "moveWordBackward", 79 "moveWordBackwardAndModifySelection", 80 "moveWordForward", 81 "moveWordForwardAndModifySelection", 82 "moveWordLeft", 83 "moveWordLeftAndModifySelection", 84 "moveWordRight", 85 "moveWordRightAndModifySelection", 86 "outdent", 87 "pageDown", 88 "pageDownAndModifySelection", 89 "pageUp", 90 "pageUpAndModifySelection", 91 "selectAll", 92 "selectLine", 93 "selectParagraph", 94 "selectSentence", 95 "selectToMark", 96 "selectWord", 97 "setMark", 98 "showGuessPanel", 99 "subscript", 100 "superscript", 101 "swapWithMark", 102 "transpose", 103 "underline", 104 "unscript", 105 "yank", 106 "yankAndSelect"}; 107 108 109 // This function is installed via the objc runtime as the implementation of all 110 // the various editing selectors. 111 // The objc runtime hookup occurs in 112 // RWHVMEditCommandHelper::AddEditingSelectorsToClass(). 113 // 114 // self - the object we're attached to; it must implement the 115 // RenderWidgetHostViewMacOwner protocol. 116 // _cmd - the selector that fired. 117 // sender - the id of the object that sent the message. 118 // 119 // The selector is translated into an edit comand and then forwarded down the 120 // pipeline to WebCore. 121 // The route the message takes is: 122 // RenderWidgetHostViewMac -> RenderViewHost -> 123 // | IPC | -> 124 // RenderView -> currently focused WebFrame. 125 // The WebFrame is in the Chrome glue layer and forwards the message to WebCore. 126 void EditCommandImp(id self, SEL _cmd, id sender) { 127 // Make sure |self| is the right type. 128 DCHECK([self conformsToProtocol:@protocol(RenderWidgetHostViewMacOwner)]); 129 130 // SEL -> command name string. 131 NSString* command_name_ns = 132 RWHVMEditCommandHelper::CommandNameForSelector(_cmd); 133 std::string edit_command([command_name_ns UTF8String]); 134 135 // Forward the edit command string down the pipeline. 136 RenderWidgetHostViewMac* rwhv = [(id<RenderWidgetHostViewMacOwner>)self 137 renderWidgetHostViewMac]; 138 DCHECK(rwhv); 139 140 // The second parameter is the core command value which isn't used here. 141 rwhv->GetRenderWidgetHost()->ForwardEditCommand(edit_command, ""); 142 } 143 144 } // namespace 145 146 // Maps an objc-selector to a core command name. 147 // 148 // Returns the core command name (which is the selector name with the trailing 149 // ':' stripped in most cases). 150 // 151 // Adapted from a function by the same name in 152 // WebKit/mac/WebView/WebHTMLView.mm . 153 // Capitalized names are returned from this function, but that's simply 154 // matching WebHTMLView.mm. 155 NSString* RWHVMEditCommandHelper::CommandNameForSelector(SEL selector) { 156 if (selector == @selector(insertParagraphSeparator:) || 157 selector == @selector(insertNewlineIgnoringFieldEditor:)) 158 return @"InsertNewline"; 159 if (selector == @selector(insertTabIgnoringFieldEditor:)) 160 return @"InsertTab"; 161 if (selector == @selector(pageDown:)) 162 return @"MovePageDown"; 163 if (selector == @selector(pageDownAndModifySelection:)) 164 return @"MovePageDownAndModifySelection"; 165 if (selector == @selector(pageUp:)) 166 return @"MovePageUp"; 167 if (selector == @selector(pageUpAndModifySelection:)) 168 return @"MovePageUpAndModifySelection"; 169 170 // Remove the trailing colon. 171 NSString* selector_str = NSStringFromSelector(selector); 172 int selector_len = [selector_str length]; 173 return [selector_str substringToIndex:selector_len - 1]; 174 } 175 176 RWHVMEditCommandHelper::RWHVMEditCommandHelper() { 177 for (size_t i = 0; i < arraysize(kEditCommands); ++i) { 178 edit_command_set_.insert(kEditCommands[i]); 179 } 180 } 181 182 RWHVMEditCommandHelper::~RWHVMEditCommandHelper() {} 183 184 // Dynamically adds Selectors to the aformentioned class. 185 void RWHVMEditCommandHelper::AddEditingSelectorsToClass(Class klass) { 186 for (size_t i = 0; i < arraysize(kEditCommands); ++i) { 187 // Append trailing ':' to command name to get selector name. 188 NSString* sel_str = [NSString stringWithFormat: @"%s:", kEditCommands[i]]; 189 190 SEL edit_selector = NSSelectorFromString(sel_str); 191 // May want to use @encode() for the last parameter to this method. 192 // If class_addMethod fails we assume that all the editing selectors where 193 // added to the class. 194 // If a certain class already implements a method then class_addMethod 195 // returns NO, which we can safely ignore. 196 class_addMethod(klass, edit_selector, (IMP)EditCommandImp, "v@:@"); 197 } 198 } 199 200 bool RWHVMEditCommandHelper::IsMenuItemEnabled(SEL item_action, 201 id<RenderWidgetHostViewMacOwner> owner) { 202 const char* selector_name = sel_getName(item_action); 203 // TODO(jeremy): The final form of this function will check state 204 // associated with the Browser. 205 206 // For now just mark all edit commands as enabled. 207 NSString* selector_name_ns = [NSString stringWithUTF8String:selector_name]; 208 209 // Remove trailing ':' 210 size_t str_len = [selector_name_ns length]; 211 selector_name_ns = [selector_name_ns substringToIndex:str_len - 1]; 212 std::string edit_command_name([selector_name_ns UTF8String]); 213 214 // search for presence in set and return. 215 bool ret = edit_command_set_.find(edit_command_name) != 216 edit_command_set_.end(); 217 return ret; 218 } 219 220 NSArray* RWHVMEditCommandHelper::GetEditSelectorNames() { 221 size_t num_edit_commands = arraysize(kEditCommands); 222 NSMutableArray* ret = [NSMutableArray arrayWithCapacity:num_edit_commands]; 223 224 for (size_t i = 0; i < num_edit_commands; ++i) { 225 [ret addObject:[NSString stringWithUTF8String:kEditCommands[i]]]; 226 } 227 228 return ret; 229 } 230