2
* Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions
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.
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.
27
#include "ContextMenu.h"
29
#include "CSSComputedStyleDeclaration.h"
30
#include "CSSProperty.h"
31
#include "CSSPropertyNames.h"
32
#include "ContextMenuController.h"
36
#include "FrameLoader.h"
38
#include "LocalizedStrings.h"
41
#include "ResourceRequest.h"
42
#include "SelectionController.h"
43
#include "TextIterator.h"
48
using namespace Unicode;
52
ContextMenuController* ContextMenu::controller() const
54
if (Node* node = m_hitTestResult.innerNonSharedNode())
55
if (Frame* frame = node->document()->frame())
56
if (Page* page = frame->page())
57
return page->contextMenuController();
61
static auto_ptr<ContextMenuItem> separatorItem()
63
return auto_ptr<ContextMenuItem>(new ContextMenuItem(SeparatorType, ContextMenuItemTagNoAction, String()));
66
static void createAndAppendFontSubMenu(const HitTestResult& result, ContextMenuItem& fontMenuItem)
68
ContextMenu fontMenu(result);
71
ContextMenuItem showFonts(ActionType, ContextMenuItemTagShowFonts, contextMenuItemTagShowFonts());
73
ContextMenuItem bold(ActionType, ContextMenuItemTagBold, contextMenuItemTagBold());
74
ContextMenuItem italic(ActionType, ContextMenuItemTagItalic, contextMenuItemTagItalic());
75
ContextMenuItem underline(ActionType, ContextMenuItemTagUnderline, contextMenuItemTagUnderline());
76
ContextMenuItem outline(ActionType, ContextMenuItemTagOutline, contextMenuItemTagOutline());
78
ContextMenuItem styles(ActionType, ContextMenuItemTagStyles, contextMenuItemTagStyles());
79
ContextMenuItem showColors(ActionType, ContextMenuItemTagShowColors, contextMenuItemTagShowColors());
83
fontMenu.appendItem(showFonts);
85
fontMenu.appendItem(bold);
86
fontMenu.appendItem(italic);
87
fontMenu.appendItem(underline);
88
fontMenu.appendItem(outline);
90
fontMenu.appendItem(styles);
91
fontMenu.appendItem(*separatorItem());
92
fontMenu.appendItem(showColors);
95
fontMenuItem.setSubMenu(&fontMenu);
98
#ifndef BUILDING_ON_TIGER
99
static void createAndAppendSpellingAndGrammarSubMenu(const HitTestResult& result, ContextMenuItem& spellingAndGrammarMenuItem)
101
ContextMenu spellingAndGrammarMenu(result);
103
ContextMenuItem showSpellingPanel(ActionType, ContextMenuItemTagShowSpellingPanel,
104
contextMenuItemTagShowSpellingPanel(true));
105
ContextMenuItem checkSpelling(ActionType, ContextMenuItemTagCheckSpelling,
106
contextMenuItemTagCheckSpelling());
107
ContextMenuItem checkAsYouType(ActionType, ContextMenuItemTagCheckSpellingWhileTyping,
108
contextMenuItemTagCheckSpellingWhileTyping());
109
ContextMenuItem grammarWithSpelling(ActionType, ContextMenuItemTagCheckGrammarWithSpelling,
110
contextMenuItemTagCheckGrammarWithSpelling());
112
spellingAndGrammarMenu.appendItem(showSpellingPanel);
113
spellingAndGrammarMenu.appendItem(checkSpelling);
114
spellingAndGrammarMenu.appendItem(checkAsYouType);
115
spellingAndGrammarMenu.appendItem(grammarWithSpelling);
117
spellingAndGrammarMenuItem.setSubMenu(&spellingAndGrammarMenu);
121
static void createAndAppendSpellingSubMenu(const HitTestResult& result, ContextMenuItem& spellingMenuItem)
123
ContextMenu spellingMenu(result);
125
ContextMenuItem showSpellingPanel(ActionType, ContextMenuItemTagShowSpellingPanel,
126
contextMenuItemTagShowSpellingPanel(true));
127
ContextMenuItem checkSpelling(ActionType, ContextMenuItemTagCheckSpelling,
128
contextMenuItemTagCheckSpelling());
129
ContextMenuItem checkAsYouType(ActionType, ContextMenuItemTagCheckSpellingWhileTyping,
130
contextMenuItemTagCheckSpellingWhileTyping());
132
spellingMenu.appendItem(showSpellingPanel);
133
spellingMenu.appendItem(checkSpelling);
134
spellingMenu.appendItem(checkAsYouType);
136
spellingMenuItem.setSubMenu(&spellingMenu);
141
static void createAndAppendSpeechSubMenu(const HitTestResult& result, ContextMenuItem& speechMenuItem)
143
ContextMenu speechMenu(result);
145
ContextMenuItem start(ActionType, ContextMenuItemTagStartSpeaking, contextMenuItemTagStartSpeaking());
146
ContextMenuItem stop(ActionType, ContextMenuItemTagStopSpeaking, contextMenuItemTagStopSpeaking());
148
speechMenu.appendItem(start);
149
speechMenu.appendItem(stop);
151
speechMenuItem.setSubMenu(&speechMenu);
155
static void createAndAppendWritingDirectionSubMenu(const HitTestResult& result, ContextMenuItem& writingDirectionMenuItem)
157
ContextMenu writingDirectionMenu(result);
159
ContextMenuItem defaultItem(ActionType, ContextMenuItemTagDefaultDirection,
160
contextMenuItemTagDefaultDirection());
161
ContextMenuItem ltr(ActionType, ContextMenuItemTagLeftToRight, contextMenuItemTagLeftToRight());
162
ContextMenuItem rtl(ActionType, ContextMenuItemTagRightToLeft, contextMenuItemTagRightToLeft());
164
writingDirectionMenu.appendItem(defaultItem);
165
writingDirectionMenu.appendItem(ltr);
166
writingDirectionMenu.appendItem(rtl);
168
writingDirectionMenuItem.setSubMenu(&writingDirectionMenu);
171
static bool selectionContainsPossibleWord(Frame* frame)
173
// Current algorithm: look for a character that's not just a separator.
174
for (TextIterator it(frame->selectionController()->toRange().get()); !it.atEnd(); it.advance()) {
175
int length = it.length();
176
const UChar* characters = it.characters();
177
for (int i = 0; i < length; ++i)
178
if (!(category(characters[i]) & (Separator_Space | Separator_Line | Separator_Paragraph)))
184
void ContextMenu::populate()
186
ContextMenuItem OpenLinkItem(ActionType, ContextMenuItemTagOpenLink, contextMenuItemTagOpenLink());
187
ContextMenuItem OpenLinkInNewWindowItem(ActionType, ContextMenuItemTagOpenLinkInNewWindow,
188
contextMenuItemTagOpenLinkInNewWindow());
189
ContextMenuItem DownloadFileItem(ActionType, ContextMenuItemTagDownloadLinkToDisk,
190
contextMenuItemTagDownloadLinkToDisk());
191
ContextMenuItem CopyLinkItem(ActionType, ContextMenuItemTagCopyLinkToClipboard,
192
contextMenuItemTagCopyLinkToClipboard());
193
ContextMenuItem OpenImageInNewWindowItem(ActionType, ContextMenuItemTagOpenImageInNewWindow,
194
contextMenuItemTagOpenImageInNewWindow());
195
ContextMenuItem DownloadImageItem(ActionType, ContextMenuItemTagDownloadImageToDisk,
196
contextMenuItemTagDownloadImageToDisk());
197
ContextMenuItem CopyImageItem(ActionType, ContextMenuItemTagCopyImageToClipboard,
198
contextMenuItemTagCopyImageToClipboard());
200
ContextMenuItem SearchSpotlightItem(ActionType, ContextMenuItemTagSearchInSpotlight,
201
contextMenuItemTagSearchInSpotlight());
202
ContextMenuItem LookInDictionaryItem(ActionType, ContextMenuItemTagLookUpInDictionary,
203
contextMenuItemTagLookUpInDictionary());
205
ContextMenuItem SearchWebItem(ActionType, ContextMenuItemTagSearchWeb, contextMenuItemTagSearchWeb());
206
ContextMenuItem CopyItem(ActionType, ContextMenuItemTagCopy, contextMenuItemTagCopy());
207
ContextMenuItem BackItem(ActionType, ContextMenuItemTagGoBack, contextMenuItemTagGoBack());
208
ContextMenuItem ForwardItem(ActionType, ContextMenuItemTagGoForward, contextMenuItemTagGoForward());
209
ContextMenuItem StopItem(ActionType, ContextMenuItemTagStop, contextMenuItemTagStop());
210
ContextMenuItem ReloadItem(ActionType, ContextMenuItemTagReload, contextMenuItemTagReload());
211
ContextMenuItem OpenFrameItem(ActionType, ContextMenuItemTagOpenFrameInNewWindow,
212
contextMenuItemTagOpenFrameInNewWindow());
213
ContextMenuItem NoGuessesItem(ActionType, ContextMenuItemTagNoGuessesFound,
214
contextMenuItemTagNoGuessesFound());
215
ContextMenuItem IgnoreSpellingItem(ActionType, ContextMenuItemTagIgnoreSpelling,
216
contextMenuItemTagIgnoreSpelling());
217
ContextMenuItem LearnSpellingItem(ActionType, ContextMenuItemTagLearnSpelling,
218
contextMenuItemTagLearnSpelling());
219
ContextMenuItem IgnoreGrammarItem(ActionType, ContextMenuItemTagIgnoreGrammar,
220
contextMenuItemTagIgnoreGrammar());
221
ContextMenuItem CutItem(ActionType, ContextMenuItemTagCut, contextMenuItemTagCut());
222
ContextMenuItem PasteItem(ActionType, ContextMenuItemTagPaste, contextMenuItemTagPaste());
224
HitTestResult result = hitTestResult();
226
Node* node = m_hitTestResult.innerNonSharedNode();
229
Frame* frame = node->document()->frame();
233
if (!result.isContentEditable()) {
234
FrameLoader* loader = frame->loader();
235
KURL linkURL = result.absoluteLinkURL();
236
if (!linkURL.isEmpty()) {
237
if (loader->canHandleRequest(ResourceRequest(linkURL))) {
238
appendItem(OpenLinkItem);
239
appendItem(OpenLinkInNewWindowItem);
240
appendItem(DownloadFileItem);
242
appendItem(CopyLinkItem);
245
KURL imageURL = result.absoluteImageURL();
246
if (!imageURL.isEmpty()) {
247
if (!linkURL.isEmpty())
248
appendItem(*separatorItem());
250
appendItem(OpenImageInNewWindowItem);
251
appendItem(DownloadImageItem);
252
if (imageURL.isLocalFile() || m_hitTestResult.image())
253
appendItem(CopyImageItem);
256
if (imageURL.isEmpty() && linkURL.isEmpty()) {
257
if (result.isSelected()) {
258
if (selectionContainsPossibleWord(frame)) {
260
appendItem(SearchSpotlightItem);
262
appendItem(SearchWebItem);
263
appendItem(*separatorItem());
265
appendItem(LookInDictionaryItem);
266
appendItem(*separatorItem());
269
appendItem(CopyItem);
271
if (loader->canGoBackOrForward(-1))
272
appendItem(BackItem);
274
if (loader->canGoBackOrForward(1))
275
appendItem(ForwardItem);
277
if (loader->isLoading())
278
appendItem(StopItem);
280
appendItem(ReloadItem);
282
if (frame->page() && frame != frame->page()->mainFrame())
283
appendItem(OpenFrameItem);
286
} else { // Make an editing context menu
287
SelectionController* selectionController = frame->selectionController();
288
bool inPasswordField = selectionController->isInPasswordField();
290
if (!inPasswordField) {
291
// Consider adding spelling-related or grammar-related context menu items (never both, since a single selected range
292
// is never considered a misspelling and bad grammar at the same time)
293
bool misspelling = frame->editor()->isSelectionMisspelled();
294
bool badGrammar = !misspelling && (frame->editor()->isGrammarCheckingEnabled() && frame->editor()->isSelectionUngrammatical());
296
if (misspelling || badGrammar) {
297
Vector<String> guesses = misspelling ? frame->editor()->guessesForMisspelledSelection()
298
: frame->editor()->guessesForUngrammaticalSelection();
299
size_t size = guesses.size();
301
// If there's bad grammar but no suggestions (e.g., repeated word), just leave off the suggestions
302
// list and trailing separator rather than adding a "No Guesses Found" item (matches AppKit)
304
appendItem(NoGuessesItem);
305
appendItem(*separatorItem());
308
for (unsigned i = 0; i < size; i++) {
309
const String &guess = guesses[i];
310
if (!guess.isEmpty()) {
311
ContextMenuItem item(ActionType, ContextMenuItemTagSpellingGuess, guess);
315
appendItem(*separatorItem());
319
appendItem(IgnoreSpellingItem);
320
appendItem(LearnSpellingItem);
322
appendItem(IgnoreGrammarItem);
323
appendItem(*separatorItem());
327
FrameLoader* loader = frame->loader();
328
KURL linkURL = result.absoluteLinkURL();
329
if (!linkURL.isEmpty()) {
330
if (loader->canHandleRequest(ResourceRequest(linkURL))) {
331
appendItem(OpenLinkItem);
332
appendItem(OpenLinkInNewWindowItem);
333
appendItem(DownloadFileItem);
335
appendItem(CopyLinkItem);
336
appendItem(*separatorItem());
339
if (result.isSelected() && !inPasswordField && selectionContainsPossibleWord(frame)) {
341
appendItem(SearchSpotlightItem);
343
appendItem(SearchWebItem);
344
appendItem(*separatorItem());
347
appendItem(LookInDictionaryItem);
348
appendItem(*separatorItem());
353
appendItem(CopyItem);
354
appendItem(PasteItem);
356
if (!inPasswordField) {
357
appendItem(*separatorItem());
358
#ifndef BUILDING_ON_TIGER
359
ContextMenuItem SpellingAndGrammarMenuItem(SubmenuType, ContextMenuItemTagSpellingMenu,
360
contextMenuItemTagSpellingMenu());
361
createAndAppendSpellingAndGrammarSubMenu(m_hitTestResult, SpellingAndGrammarMenuItem);
362
appendItem(SpellingAndGrammarMenuItem);
364
ContextMenuItem SpellingMenuItem(SubmenuType, ContextMenuItemTagSpellingMenu,
365
contextMenuItemTagSpellingMenu());
366
createAndAppendSpellingSubMenu(m_hitTestResult, SpellingMenuItem);
367
appendItem(SpellingMenuItem);
369
ContextMenuItem FontMenuItem(SubmenuType, ContextMenuItemTagFontMenu,
370
contextMenuItemTagFontMenu());
371
createAndAppendFontSubMenu(m_hitTestResult, FontMenuItem);
372
appendItem(FontMenuItem);
374
ContextMenuItem SpeechMenuItem(SubmenuType, ContextMenuItemTagSpeechMenu,
375
contextMenuItemTagSpeechMenu());
376
createAndAppendSpeechSubMenu(m_hitTestResult, SpeechMenuItem);
377
appendItem(SpeechMenuItem);
379
ContextMenuItem WritingDirectionMenuItem(SubmenuType, ContextMenuItemTagWritingDirectionMenu,
380
contextMenuItemTagWritingDirectionMenu());
381
createAndAppendWritingDirectionSubMenu(m_hitTestResult, WritingDirectionMenuItem);
382
appendItem(WritingDirectionMenuItem);
387
void ContextMenu::addInspectElementItem()
389
Node* node = m_hitTestResult.innerNonSharedNode();
393
Frame* frame = node->document()->frame();
397
Page* page = frame->page();
401
if (!page->inspectorController())
404
ContextMenuItem InspectElementItem(ActionType, ContextMenuItemTagInspectElement, contextMenuItemTagInspectElement());
405
appendItem(*separatorItem());
406
appendItem(InspectElementItem);
409
static bool triStateToBool(Frame::TriState state)
411
return state == Frame::trueTriState;
414
void ContextMenu::checkOrEnableIfNeeded(ContextMenuItem& item) const
416
if (item.type() == SeparatorType)
419
Frame* frame = m_hitTestResult.innerNonSharedNode()->document()->frame();
423
bool shouldEnable = true;
424
bool shouldCheck = false;
426
switch (item.action()) {
427
case ContextMenuItemTagCheckSpelling:
428
shouldEnable = frame->editor()->canEdit();
430
case ContextMenuItemTagDefaultDirection:
432
shouldEnable = false;
434
case ContextMenuItemTagLeftToRight:
435
case ContextMenuItemTagRightToLeft: {
436
ExceptionCode ec = 0;
437
RefPtr<CSSStyleDeclaration> style = frame->document()->createCSSStyleDeclaration();
438
String direction = item.action() == ContextMenuItemTagLeftToRight ? "ltr" : "rtl";
439
style->setProperty(CSS_PROP_DIRECTION, direction, false, ec);
440
shouldCheck = triStateToBool(frame->selectionHasStyle(style.get()));
444
case ContextMenuItemTagCopy:
445
shouldEnable = frame->editor()->canDHTMLCopy() || frame->editor()->canCopy();
447
case ContextMenuItemTagCut:
448
shouldEnable = frame->editor()->canDHTMLCut() || frame->editor()->canCut();
450
case ContextMenuItemTagIgnoreSpelling:
451
case ContextMenuItemTagLearnSpelling:
452
shouldEnable = frame->selectionController()->isRange();
454
case ContextMenuItemTagPaste:
455
shouldEnable = frame->editor()->canDHTMLPaste() || frame->editor()->canPaste();
457
case ContextMenuItemTagUnderline: {
458
ExceptionCode ec = 0;
459
RefPtr<CSSStyleDeclaration> style = frame->document()->createCSSStyleDeclaration();
460
style->setProperty(CSS_PROP__WEBKIT_TEXT_DECORATIONS_IN_EFFECT, "underline", false, ec);
461
shouldCheck = triStateToBool(frame->selectionHasStyle(style.get()));
462
shouldEnable = frame->editor()->canEditRichly();
465
case ContextMenuItemTagLookUpInDictionary:
466
shouldEnable = frame->selectionController()->isRange();
468
case ContextMenuItemTagCheckGrammarWithSpelling:
469
#ifndef BUILDING_ON_TIGER
470
if (frame->editor()->isGrammarCheckingEnabled())
475
case ContextMenuItemTagItalic: {
476
ExceptionCode ec = 0;
477
RefPtr<CSSStyleDeclaration> style = frame->document()->createCSSStyleDeclaration();
478
style->setProperty(CSS_PROP_FONT_STYLE, "italic", false, ec);
479
shouldCheck = triStateToBool(frame->selectionHasStyle(style.get()));
480
shouldEnable = frame->editor()->canEditRichly();
483
case ContextMenuItemTagBold: {
484
ExceptionCode ec = 0;
485
RefPtr<CSSStyleDeclaration> style = frame->document()->createCSSStyleDeclaration();
486
style->setProperty(CSS_PROP_FONT_WEIGHT, "bold", false, ec);
487
shouldCheck = triStateToBool(frame->selectionHasStyle(style.get()));
488
shouldEnable = frame->editor()->canEditRichly();
491
case ContextMenuItemTagOutline:
492
shouldEnable = false;
494
case ContextMenuItemTagShowSpellingPanel:
495
#ifndef BUILDING_ON_TIGER
496
if (frame->editor()->spellingPanelIsShowing())
497
item.setTitle(contextMenuItemTagShowSpellingPanel(false));
499
item.setTitle(contextMenuItemTagShowSpellingPanel(true));
501
shouldEnable = frame->editor()->canEdit();
503
case ContextMenuItemTagNoGuessesFound:
504
shouldEnable = false;
506
case ContextMenuItemTagCheckSpellingWhileTyping:
507
shouldCheck = frame->editor()->isContinuousSpellCheckingEnabled();
509
case ContextMenuItemTagNoAction:
510
case ContextMenuItemTagOpenLinkInNewWindow:
511
case ContextMenuItemTagDownloadLinkToDisk:
512
case ContextMenuItemTagCopyLinkToClipboard:
513
case ContextMenuItemTagOpenImageInNewWindow:
514
case ContextMenuItemTagDownloadImageToDisk:
515
case ContextMenuItemTagCopyImageToClipboard:
516
case ContextMenuItemTagOpenFrameInNewWindow:
517
case ContextMenuItemTagGoBack:
518
case ContextMenuItemTagGoForward:
519
case ContextMenuItemTagStop:
520
case ContextMenuItemTagReload:
521
case ContextMenuItemTagSpellingGuess:
522
case ContextMenuItemTagOther:
523
case ContextMenuItemTagSearchInSpotlight:
524
case ContextMenuItemTagSearchWeb:
525
case ContextMenuItemTagOpenWithDefaultApplication:
526
case ContextMenuItemPDFActualSize:
527
case ContextMenuItemPDFZoomIn:
528
case ContextMenuItemPDFZoomOut:
529
case ContextMenuItemPDFAutoSize:
530
case ContextMenuItemPDFSinglePage:
531
case ContextMenuItemPDFFacingPages:
532
case ContextMenuItemPDFContinuous:
533
case ContextMenuItemPDFNextPage:
534
case ContextMenuItemPDFPreviousPage:
535
case ContextMenuItemTagOpenLink:
536
case ContextMenuItemTagIgnoreGrammar:
537
case ContextMenuItemTagSpellingMenu:
538
case ContextMenuItemTagFontMenu:
539
case ContextMenuItemTagShowFonts:
540
case ContextMenuItemTagStyles:
541
case ContextMenuItemTagShowColors:
542
case ContextMenuItemTagSpeechMenu:
543
case ContextMenuItemTagStartSpeaking:
544
case ContextMenuItemTagStopSpeaking:
545
case ContextMenuItemTagWritingDirectionMenu:
546
case ContextMenuItemTagPDFSinglePageScrolling:
547
case ContextMenuItemTagPDFFacingPagesScrolling:
548
case ContextMenuItemTagInspectElement:
549
case ContextMenuItemBaseApplicationTag:
553
item.setChecked(shouldCheck);
554
item.setEnabled(shouldEnable);