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
ContextMenuController(Page * page,ContextMenuClient * client)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
~ContextMenuController()75 ContextMenuController::~ContextMenuController()
76 {
77 m_client->contextMenuDestroyed();
78 }
79
clearContextMenu()80 void ContextMenuController::clearContextMenu()
81 {
82 m_contextMenu.set(0);
83 if (m_menuProvider)
84 m_menuProvider->contextMenuCleared();
85 m_menuProvider = 0;
86 }
87
handleContextMenuEvent(Event * event)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
showContextMenu(Event * event,PassRefPtr<ContextMenuProvider> menuProvider)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
createContextMenu(Event * event)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
showContextMenu(Event * event)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
openNewWindow(const KURL & urlToLoad,Frame * frame)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
contextMenuItemSelected(ContextMenuItem * item)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