Home | History | Annotate | Download | only in page
      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