|
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) |