1 /* 2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "ContextMenuController.h" 28 29 #if ENABLE(CONTEXT_MENUS) 30 31 #include "Chrome.h" 32 #include "ContextMenu.h" 33 #include "ContextMenuClient.h" 34 #include "ContextMenuProvider.h" 35 #include "Document.h" 36 #include "DocumentFragment.h" 37 #include "DocumentLoader.h" 38 #include "Editor.h" 39 #include "EditorClient.h" 40 #include "Event.h" 41 #include "EventHandler.h" 42 #include "EventNames.h" 43 #include "FormState.h" 44 #include "Frame.h" 45 #include "FrameLoadRequest.h" 46 #include "FrameLoader.h" 47 #include "HTMLFormElement.h" 48 #include "HitTestRequest.h" 49 #include "HitTestResult.h" 50 #include "InspectorController.h" 51 #include "MouseEvent.h" 52 #include "Node.h" 53 #include "Page.h" 54 #include "RenderLayer.h" 55 #include "RenderObject.h" 56 #include "ReplaceSelectionCommand.h" 57 #include "ResourceRequest.h" 58 #include "SelectionController.h" 59 #include "Settings.h" 60 #include "TextIterator.h" 61 #include "WindowFeatures.h" 62 #include "markup.h" 63 64 namespace WebCore { 65 66 ContextMenuController::ContextMenuController(Page* page, ContextMenuClient* client) 67 : m_page(page) 68 , m_client(client) 69 , m_contextMenu(0) 70 { 71 ASSERT_ARG(page, page); 72 ASSERT_ARG(client, client); 73 } 74 75 ContextMenuController::~ContextMenuController() 76 { 77 m_client->contextMenuDestroyed(); 78 } 79 80 void ContextMenuController::clearContextMenu() 81 { 82 m_contextMenu.set(0); 83 if (m_menuProvider) 84 m_menuProvider->contextMenuCleared(); 85 m_menuProvider = 0; 86 } 87 88 void ContextMenuController::handleContextMenuEvent(Event* event) 89 { 90 m_contextMenu.set(createContextMenu(event)); 91 if (!m_contextMenu) 92 return; 93 m_contextMenu->populate(); 94 showContextMenu(event); 95 } 96 97 void ContextMenuController::showContextMenu(Event* event, PassRefPtr<ContextMenuProvider> menuProvider) 98 { 99 m_menuProvider = menuProvider; 100 101 m_contextMenu.set(createContextMenu(event)); 102 if (!m_contextMenu) { 103 clearContextMenu(); 104 return; 105 } 106 107 m_menuProvider->populateContextMenu(m_contextMenu.get()); 108 showContextMenu(event); 109 } 110 111 ContextMenu* ContextMenuController::createContextMenu(Event* event) 112 { 113 if (!event->isMouseEvent()) 114 return 0; 115 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); 116 HitTestResult result(mouseEvent->absoluteLocation()); 117 118 if (Frame* frame = event->target()->toNode()->document()->frame()) 119 result = frame->eventHandler()->hitTestResultAtPoint(mouseEvent->absoluteLocation(), false); 120 121 if (!result.innerNonSharedNode()) 122 return 0; 123 return new ContextMenu(result); 124 } 125 126 void ContextMenuController::showContextMenu(Event* event) 127 { 128 #if ENABLE(INSPECTOR) 129 if (m_page->inspectorController()->enabled()) 130 m_contextMenu->addInspectElementItem(); 131 #endif 132 PlatformMenuDescription customMenu = m_client->getCustomMenuFromDefaultItems(m_contextMenu.get()); 133 m_contextMenu->setPlatformDescription(customMenu); 134 event->setDefaultHandled(); 135 } 136 137 static void openNewWindow(const KURL& urlToLoad, Frame* frame) 138 { 139 if (Page* oldPage = frame->page()) { 140 WindowFeatures features; 141 if (Page* newPage = oldPage->chrome()->createWindow(frame, FrameLoadRequest(ResourceRequest(urlToLoad, frame->loader()->outgoingReferrer())), features)) 142 newPage->chrome()->show(); 143 } 144 } 145 146 void ContextMenuController::contextMenuItemSelected(ContextMenuItem* item) 147 { 148 ASSERT(item->type() == ActionType || item->type() == CheckableActionType); 149 150 if (item->action() >= ContextMenuItemBaseApplicationTag) { 151 m_client->contextMenuItemSelected(item, m_contextMenu.get()); 152 return; 153 } 154 155 if (item->action() >= ContextMenuItemBaseCustomTag) { 156 ASSERT(m_menuProvider); 157 m_menuProvider->contextMenuItemSelected(item); 158 return; 159 } 160 161 HitTestResult result = m_contextMenu->hitTestResult(); 162 Frame* frame = result.innerNonSharedNode()->document()->frame(); 163 if (!frame) 164 return; 165 166 switch (item->action()) { 167 case ContextMenuItemTagOpenLinkInNewWindow: 168 openNewWindow(result.absoluteLinkURL(), frame); 169 break; 170 case ContextMenuItemTagDownloadLinkToDisk: 171 // FIXME: Some day we should be able to do this from within WebCore. 172 m_client->downloadURL(result.absoluteLinkURL()); 173 break; 174 case ContextMenuItemTagCopyLinkToClipboard: 175 frame->editor()->copyURL(result.absoluteLinkURL(), result.textContent()); 176 break; 177 case ContextMenuItemTagOpenImageInNewWindow: 178 openNewWindow(result.absoluteImageURL(), frame); 179 break; 180 case ContextMenuItemTagDownloadImageToDisk: 181 // FIXME: Some day we should be able to do this from within WebCore. 182 m_client->downloadURL(result.absoluteImageURL()); 183 break; 184 case ContextMenuItemTagCopyImageToClipboard: 185 // FIXME: The Pasteboard class is not written yet 186 // For now, call into the client. This is temporary! 187 frame->editor()->copyImage(result); 188 break; 189 case ContextMenuItemTagOpenFrameInNewWindow: { 190 DocumentLoader* loader = frame->loader()->documentLoader(); 191 if (!loader->unreachableURL().isEmpty()) 192 openNewWindow(loader->unreachableURL(), frame); 193 else 194 openNewWindow(loader->url(), frame); 195 break; 196 } 197 case ContextMenuItemTagCopy: 198 frame->editor()->copy(); 199 break; 200 case ContextMenuItemTagGoBack: 201 if (Page* page = frame->page()) 202 page->goBackOrForward(-1); 203 break; 204 case ContextMenuItemTagGoForward: 205 if (Page* page = frame->page()) 206 page->goBackOrForward(1); 207 break; 208 case ContextMenuItemTagStop: 209 frame->loader()->stop(); 210 break; 211 case ContextMenuItemTagReload: 212 frame->loader()->reload(); 213 break; 214 case ContextMenuItemTagCut: 215 frame->editor()->cut(); 216 break; 217 case ContextMenuItemTagPaste: 218 frame->editor()->paste(); 219 break; 220 #if PLATFORM(GTK) 221 case ContextMenuItemTagDelete: 222 frame->editor()->performDelete(); 223 break; 224 case ContextMenuItemTagSelectAll: 225 frame->editor()->command("SelectAll").execute(); 226 break; 227 #endif 228 case ContextMenuItemTagSpellingGuess: 229 ASSERT(frame->selectedText().length()); 230 if (frame->editor()->shouldInsertText(item->title(), frame->selection()->toNormalizedRange().get(), EditorInsertActionPasted)) { 231 Document* document = frame->document(); 232 RefPtr<ReplaceSelectionCommand> command = ReplaceSelectionCommand::create(document, createFragmentFromMarkup(document, item->title(), ""), true, false, true); 233 applyCommand(command); 234 frame->revealSelection(ScrollAlignment::alignToEdgeIfNeeded); 235 } 236 break; 237 case ContextMenuItemTagIgnoreSpelling: 238 frame->editor()->ignoreSpelling(); 239 break; 240 case ContextMenuItemTagLearnSpelling: 241 frame->editor()->learnSpelling(); 242 break; 243 case ContextMenuItemTagSearchWeb: 244 m_client->searchWithGoogle(frame); 245 break; 246 case ContextMenuItemTagLookUpInDictionary: 247 // FIXME: Some day we may be able to do this from within WebCore. 248 m_client->lookUpInDictionary(frame); 249 break; 250 case ContextMenuItemTagOpenLink: 251 if (Frame* targetFrame = result.targetFrame()) 252 targetFrame->loader()->loadFrameRequest(FrameLoadRequest(ResourceRequest(result.absoluteLinkURL(), frame->loader()->outgoingReferrer())), false, false, 0, 0, SendReferrer); 253 else 254 openNewWindow(result.absoluteLinkURL(), frame); 255 break; 256 case ContextMenuItemTagBold: 257 frame->editor()->command("ToggleBold").execute(); 258 break; 259 case ContextMenuItemTagItalic: 260 frame->editor()->command("ToggleItalic").execute(); 261 break; 262 case ContextMenuItemTagUnderline: 263 frame->editor()->toggleUnderline(); 264 break; 265 case ContextMenuItemTagOutline: 266 // We actually never enable this because CSS does not have a way to specify an outline font, 267 // which may make this difficult to implement. Maybe a special case of text-shadow? 268 break; 269 case ContextMenuItemTagStartSpeaking: { 270 ExceptionCode ec; 271 RefPtr<Range> selectedRange = frame->selection()->toNormalizedRange(); 272 if (!selectedRange || selectedRange->collapsed(ec)) { 273 Document* document = result.innerNonSharedNode()->document(); 274 selectedRange = document->createRange(); 275 selectedRange->selectNode(document->documentElement(), ec); 276 } 277 m_client->speak(plainText(selectedRange.get())); 278 break; 279 } 280 case ContextMenuItemTagStopSpeaking: 281 m_client->stopSpeaking(); 282 break; 283 case ContextMenuItemTagDefaultDirection: 284 frame->editor()->setBaseWritingDirection(NaturalWritingDirection); 285 break; 286 case ContextMenuItemTagLeftToRight: 287 frame->editor()->setBaseWritingDirection(LeftToRightWritingDirection); 288 break; 289 case ContextMenuItemTagRightToLeft: 290 frame->editor()->setBaseWritingDirection(RightToLeftWritingDirection); 291 break; 292 case ContextMenuItemTagTextDirectionDefault: 293 frame->editor()->command("MakeTextWritingDirectionNatural").execute(); 294 break; 295 case ContextMenuItemTagTextDirectionLeftToRight: 296 frame->editor()->command("MakeTextWritingDirectionLeftToRight").execute(); 297 break; 298 case ContextMenuItemTagTextDirectionRightToLeft: 299 frame->editor()->command("MakeTextWritingDirectionRightToLeft").execute(); 300 break; 301 #if PLATFORM(MAC) 302 case ContextMenuItemTagSearchInSpotlight: 303 m_client->searchWithSpotlight(); 304 break; 305 #endif 306 case ContextMenuItemTagShowSpellingPanel: 307 frame->editor()->showSpellingGuessPanel(); 308 break; 309 case ContextMenuItemTagCheckSpelling: 310 frame->editor()->advanceToNextMisspelling(); 311 break; 312 case ContextMenuItemTagCheckSpellingWhileTyping: 313 frame->editor()->toggleContinuousSpellChecking(); 314 break; 315 #ifndef BUILDING_ON_TIGER 316 case ContextMenuItemTagCheckGrammarWithSpelling: 317 frame->editor()->toggleGrammarChecking(); 318 break; 319 #endif 320 #if PLATFORM(MAC) 321 case ContextMenuItemTagShowFonts: 322 frame->editor()->showFontPanel(); 323 break; 324 case ContextMenuItemTagStyles: 325 frame->editor()->showStylesPanel(); 326 break; 327 case ContextMenuItemTagShowColors: 328 frame->editor()->showColorPanel(); 329 break; 330 #endif 331 #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 332 case ContextMenuItemTagMakeUpperCase: 333 frame->editor()->uppercaseWord(); 334 break; 335 case ContextMenuItemTagMakeLowerCase: 336 frame->editor()->lowercaseWord(); 337 break; 338 case ContextMenuItemTagCapitalize: 339 frame->editor()->capitalizeWord(); 340 break; 341 case ContextMenuItemTagShowSubstitutions: 342 frame->editor()->showSubstitutionsPanel(); 343 break; 344 case ContextMenuItemTagSmartCopyPaste: 345 frame->editor()->toggleSmartInsertDelete(); 346 break; 347 case ContextMenuItemTagSmartQuotes: 348 frame->editor()->toggleAutomaticQuoteSubstitution(); 349 break; 350 case ContextMenuItemTagSmartDashes: 351 frame->editor()->toggleAutomaticDashSubstitution(); 352 break; 353 case ContextMenuItemTagSmartLinks: 354 frame->editor()->toggleAutomaticLinkDetection(); 355 break; 356 case ContextMenuItemTagTextReplacement: 357 frame->editor()->toggleAutomaticTextReplacement(); 358 break; 359 case ContextMenuItemTagCorrectSpellingAutomatically: 360 frame->editor()->toggleAutomaticSpellingCorrection(); 361 break; 362 case ContextMenuItemTagChangeBack: 363 frame->editor()->changeBackToReplacedString(result.replacedString()); 364 break; 365 #endif 366 #if ENABLE(INSPECTOR) 367 case ContextMenuItemTagInspectElement: 368 if (Page* page = frame->page()) 369 page->inspectorController()->inspect(result.innerNonSharedNode()); 370 break; 371 #endif 372 default: 373 break; 374 } 375 } 376 377 } // namespace WebCore 378 379 #endif // ENABLE(CONTEXT_MENUS) 380