2
* Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3
* Copyright (C) 2006 Zack Rusin <zack@kde.org>
4
* Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
5
* Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
9
* Redistribution and use in source and binary forms, with or without
10
* modification, are permitted provided that the following conditions
12
* 1. Redistributions of source code must retain the above copyright
13
* notice, this list of conditions and the following disclaimer.
14
* 2. Redistributions in binary form must reproduce the above copyright
15
* notice, this list of conditions and the following disclaimer in the
16
* documentation and/or other materials provided with the distribution.
18
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
19
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
#include "EditorClientQt.h"
35
#include "UndoStepQt.h"
37
#include "FocusController.h"
39
#include "HTMLElement.h"
40
#include "HTMLInputElement.h"
41
#include "HTMLNames.h"
42
#include "KeyboardEvent.h"
43
#include "NotImplemented.h"
45
#include "Pasteboard.h"
46
#include "PlatformKeyboardEvent.h"
47
#include "QWebPageClient.h"
50
#include "SpatialNavigation.h"
51
#include "StylePropertySet.h"
52
#include "WindowsKeyboardCodes.h"
53
#include "qguiapplication.h"
55
#include "qwebpage_p.h"
60
#include <wtf/OwnPtr.h>
63
static QString dumpPath(WebCore::Node *node)
65
QString str = node->nodeName();
67
WebCore::Node *parent = node->parentNode();
69
str.append(QLatin1String(" > "));
70
str.append(parent->nodeName());
71
parent = parent->parentNode();
76
static QString dumpRange(WebCore::Range *range)
79
return QLatin1String("(null)");
80
WebCore::ExceptionCode code;
82
QString str = QString::fromLatin1("range from %1 of %2 to %3 of %4")
83
.arg(range->startOffset(code)).arg(dumpPath(range->startContainer(code)))
84
.arg(range->endOffset(code)).arg(dumpPath(range->endContainer(code)));
92
bool EditorClientQt::dumpEditingCallbacks = false;
93
bool EditorClientQt::acceptsEditing = true;
95
using namespace HTMLNames;
97
bool EditorClientQt::shouldDeleteRange(Range* range)
99
if (dumpEditingCallbacks)
100
printf("EDITING DELEGATE: shouldDeleteDOMRange:%s\n", dumpRange(range).toUtf8().constData());
105
bool EditorClientQt::shouldShowDeleteInterface(HTMLElement* element)
107
if (QWebPagePrivate::drtRun)
108
return element->getAttribute(classAttr) == "needsDeletionUI";
112
bool EditorClientQt::isContinuousSpellCheckingEnabled()
114
return m_textCheckerClient.isContinousSpellCheckingEnabled();
117
bool EditorClientQt::isGrammarCheckingEnabled()
119
return m_textCheckerClient.isGrammarCheckingEnabled();
122
int EditorClientQt::spellCheckerDocumentTag()
127
bool EditorClientQt::shouldBeginEditing(WebCore::Range* range)
129
if (dumpEditingCallbacks)
130
printf("EDITING DELEGATE: shouldBeginEditingInDOMRange:%s\n", dumpRange(range).toUtf8().constData());
134
bool EditorClientQt::shouldEndEditing(WebCore::Range* range)
136
if (dumpEditingCallbacks)
137
printf("EDITING DELEGATE: shouldEndEditingInDOMRange:%s\n", dumpRange(range).toUtf8().constData());
141
bool EditorClientQt::shouldInsertText(const String& string, Range* range, EditorInsertAction action)
143
if (dumpEditingCallbacks) {
144
static const char *insertactionstring[] = {
145
"WebViewInsertActionTyped",
146
"WebViewInsertActionPasted",
147
"WebViewInsertActionDropped",
150
printf("EDITING DELEGATE: shouldInsertText:%s replacingDOMRange:%s givenAction:%s\n",
151
QString(string).toUtf8().constData(), dumpRange(range).toUtf8().constData(), insertactionstring[action]);
153
return acceptsEditing;
156
bool EditorClientQt::shouldChangeSelectedRange(Range* currentRange, Range* proposedRange, EAffinity selectionAffinity, bool stillSelecting)
158
if (dumpEditingCallbacks) {
159
static const char *affinitystring[] = {
160
"NSSelectionAffinityUpstream",
161
"NSSelectionAffinityDownstream"
163
static const char *boolstring[] = {
168
printf("EDITING DELEGATE: shouldChangeSelectedDOMRange:%s toDOMRange:%s affinity:%s stillSelecting:%s\n",
169
dumpRange(currentRange).toUtf8().constData(),
170
dumpRange(proposedRange).toUtf8().constData(),
171
affinitystring[selectionAffinity], boolstring[stillSelecting]);
173
return acceptsEditing;
176
bool EditorClientQt::shouldApplyStyle(WebCore::StylePropertySet* style,
177
WebCore::Range* range)
179
if (dumpEditingCallbacks)
180
printf("EDITING DELEGATE: shouldApplyStyle:%s toElementsInDOMRange:%s\n",
181
QString(style->asText()).toUtf8().constData(), dumpRange(range).toUtf8().constData());
182
return acceptsEditing;
185
bool EditorClientQt::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
191
void EditorClientQt::didBeginEditing()
193
if (dumpEditingCallbacks)
194
printf("EDITING DELEGATE: webViewDidBeginEditing:WebViewDidBeginEditingNotification\n");
198
void EditorClientQt::respondToChangedContents()
200
if (dumpEditingCallbacks)
201
printf("EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification\n");
202
m_page->d->updateEditorActions();
204
emit m_page->contentsChanged();
207
void EditorClientQt::respondToChangedSelection(Frame* frame)
209
if (dumpEditingCallbacks)
210
printf("EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification\n");
211
// const Selection &selection = m_page->d->page->selection();
212
// char buffer[1024];
213
// selection.formatForDebugger(buffer, sizeof(buffer));
214
// printf("%s\n", buffer);
216
if (supportsGlobalSelection() && frame->selection()->isRange()) {
217
bool oldSelectionMode = Pasteboard::generalPasteboard()->isSelectionMode();
218
Pasteboard::generalPasteboard()->setSelectionMode(true);
219
Pasteboard::generalPasteboard()->writeSelection(frame->selection()->toNormalizedRange().get(), frame->editor()->canSmartCopyOrDelete(), frame);
220
Pasteboard::generalPasteboard()->setSelectionMode(oldSelectionMode);
223
m_page->d->updateEditorActions();
224
emit m_page->selectionChanged();
225
if (!frame->editor()->ignoreCompositionSelectionChange())
226
emit m_page->microFocusChanged();
229
void EditorClientQt::didEndEditing()
231
if (dumpEditingCallbacks)
232
printf("EDITING DELEGATE: webViewDidEndEditing:WebViewDidEndEditingNotification\n");
236
void EditorClientQt::didWriteSelectionToPasteboard()
240
void EditorClientQt::didSetSelectionTypesForPasteboard()
244
bool EditorClientQt::selectWordBeforeMenuEvent()
250
void EditorClientQt::registerUndoStep(WTF::PassRefPtr<WebCore::UndoStep> step)
252
#ifndef QT_NO_UNDOSTACK
253
Frame* frame = m_page->d->page->focusController()->focusedOrMainFrame();
254
if (m_inUndoRedo || (frame && !frame->editor()->lastEditCommand() /* HACK!! Don't recreate undos */))
256
m_page->undoStack()->push(new UndoStepQt(step));
257
#endif // QT_NO_UNDOSTACK
260
void EditorClientQt::registerRedoStep(WTF::PassRefPtr<WebCore::UndoStep>)
264
void EditorClientQt::clearUndoRedoOperations()
266
#ifndef QT_NO_UNDOSTACK
267
return m_page->undoStack()->clear();
271
bool EditorClientQt::canCopyCut(WebCore::Frame*, bool defaultValue) const
276
bool EditorClientQt::canPaste(WebCore::Frame*, bool defaultValue) const
281
bool EditorClientQt::canUndo() const
283
#ifdef QT_NO_UNDOSTACK
286
return m_page->undoStack()->canUndo();
290
bool EditorClientQt::canRedo() const
292
#ifdef QT_NO_UNDOSTACK
295
return m_page->undoStack()->canRedo();
299
void EditorClientQt::undo()
301
#ifndef QT_NO_UNDOSTACK
303
m_page->undoStack()->undo();
304
m_inUndoRedo = false;
308
void EditorClientQt::redo()
310
#ifndef QT_NO_UNDOSTACK
312
m_page->undoStack()->redo();
313
m_inUndoRedo = false;
317
bool EditorClientQt::shouldInsertNode(Node* node, Range* range, EditorInsertAction action)
319
if (dumpEditingCallbacks) {
320
static const char *insertactionstring[] = {
321
"WebViewInsertActionTyped",
322
"WebViewInsertActionPasted",
323
"WebViewInsertActionDropped",
326
printf("EDITING DELEGATE: shouldInsertNode:%s replacingDOMRange:%s givenAction:%s\n", dumpPath(node).toUtf8().constData(),
327
dumpRange(range).toUtf8().constData(), insertactionstring[action]);
329
return acceptsEditing;
332
void EditorClientQt::pageDestroyed()
337
bool EditorClientQt::smartInsertDeleteEnabled()
339
return m_page->d->smartInsertDeleteEnabled;
342
void EditorClientQt::toggleSmartInsertDelete()
344
bool current = m_page->d->smartInsertDeleteEnabled;
345
m_page->d->smartInsertDeleteEnabled = !current;
348
bool EditorClientQt::isSelectTrailingWhitespaceEnabled()
350
return m_page->d->selectTrailingWhitespaceEnabled;
353
void EditorClientQt::toggleContinuousSpellChecking()
355
m_textCheckerClient.toggleContinousSpellChecking();
358
void EditorClientQt::toggleGrammarChecking()
360
return m_textCheckerClient.toggleGrammarChecking();
363
static const unsigned CtrlKey = 1 << 0;
364
static const unsigned AltKey = 1 << 1;
365
static const unsigned ShiftKey = 1 << 2;
367
struct KeyDownEntry {
370
const char* editorCommand;
373
// Handle here key down events that are needed for spatial navigation and caret browsing, or
374
// are not handled by QWebPage.
375
static const KeyDownEntry keyDownEntries[] = {
376
// Ones that do not have an associated QAction:
377
{ VK_DELETE, 0, "DeleteForward" },
378
{ VK_BACK, ShiftKey, "DeleteBackward" },
379
{ VK_BACK, 0, "DeleteBackward" },
380
// Ones that need special handling for caret browsing:
381
{ VK_PRIOR, 0, "MovePageUp" },
382
{ VK_PRIOR, ShiftKey, "MovePageUpAndModifySelection" },
383
{ VK_NEXT, 0, "MovePageDown" },
384
{ VK_NEXT, ShiftKey, "MovePageDownAndModifySelection" },
385
// Ones that need special handling for spatial navigation:
386
{ VK_LEFT, 0, "MoveLeft" },
387
{ VK_RIGHT, 0, "MoveRight" },
388
{ VK_UP, 0, "MoveUp" },
389
{ VK_DOWN, 0, "MoveDown" },
392
const char* editorCommandForKeyDownEvent(const KeyboardEvent* event)
394
if (event->type() != eventNames().keydownEvent)
397
static HashMap<int, const char*> keyDownCommandsMap;
398
if (keyDownCommandsMap.isEmpty()) {
400
unsigned numEntries = sizeof(keyDownEntries) / sizeof((keyDownEntries)[0]);
401
for (unsigned i = 0; i < numEntries; i++)
402
keyDownCommandsMap.set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].editorCommand);
405
unsigned modifiers = 0;
406
if (event->shiftKey())
407
modifiers |= ShiftKey;
410
if (event->ctrlKey())
411
modifiers |= CtrlKey;
413
int mapKey = modifiers << 16 | event->keyCode();
414
return mapKey ? keyDownCommandsMap.get(mapKey) : 0;
417
void EditorClientQt::handleKeyboardEvent(KeyboardEvent* event)
419
Frame* frame = m_page->d->page->focusController()->focusedOrMainFrame();
423
const PlatformKeyboardEvent* kevent = event->keyEvent();
424
if (!kevent || kevent->type() == PlatformEvent::KeyUp)
427
Node* start = frame->selection()->start().containerNode();
431
// FIXME: refactor all of this to use Actions or something like them
432
if (start->isContentEditable()) {
433
bool doSpatialNavigation = false;
434
if (isSpatialNavigationEnabled(frame)) {
435
if (!kevent->modifiers()) {
436
switch (kevent->windowsVirtualKeyCode()) {
441
doSpatialNavigation = true;
446
#ifndef QT_NO_SHORTCUT
447
QWebPage::WebAction action = QWebPagePrivate::editorActionForKeyEvent(kevent->qtEvent());
448
if (action != QWebPage::NoWebAction && !doSpatialNavigation) {
449
const char* cmd = QWebPagePrivate::editorCommandForWebActions(action);
450
// WebKit doesn't have enough information about mode to decide how commands that just insert text if executed via Editor should be treated,
451
// so we leave it upon WebCore to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated
452
// (e.g. Tab that inserts a Tab character, or Enter).
453
if (cmd && frame->editor()->command(cmd).isTextInsertion()
454
&& kevent->type() == PlatformEvent::RawKeyDown)
457
m_page->triggerAction(action);
458
event->setDefaultHandled();
461
#endif // QT_NO_SHORTCUT
463
String commandName = editorCommandForKeyDownEvent(event);
464
if (!commandName.isEmpty()) {
465
if (frame->editor()->command(commandName).execute()) // Event handled.
466
event->setDefaultHandled();
470
if (kevent->windowsVirtualKeyCode() == VK_TAB) {
471
// Do not handle TAB text insertion here.
476
bool shouldInsertText = false;
477
if (kevent->type() != PlatformEvent::KeyDown && !kevent->text().isEmpty()) {
479
if (kevent->ctrlKey()) {
480
if (kevent->altKey())
481
shouldInsertText = true;
484
// We need to exclude checking for Alt because it is just a different Shift
485
if (!kevent->altKey())
487
shouldInsertText = true;
492
if (shouldInsertText) {
493
frame->editor()->insertText(kevent->text(), event);
494
event->setDefaultHandled();
499
// Event not handled.
503
// Non editable content.
504
if (m_page->handle()->page->settings()->caretBrowsingEnabled()) {
505
switch (kevent->windowsVirtualKeyCode()) {
513
#ifndef QT_NO_SHORTCUT
514
QWebPage::WebAction action = QWebPagePrivate::editorActionForKeyEvent(kevent->qtEvent());
515
ASSERT(action != QWebPage::NoWebAction);
516
m_page->triggerAction(action);
517
event->setDefaultHandled();
521
case VK_PRIOR: // PageUp
522
case VK_NEXT: // PageDown
524
String commandName = editorCommandForKeyDownEvent(event);
525
ASSERT(!commandName.isEmpty());
526
frame->editor()->command(commandName).execute();
527
event->setDefaultHandled();
533
#ifndef QT_NO_SHORTCUT
534
if (kevent->qtEvent() == QKeySequence::Copy) {
535
m_page->triggerAction(QWebPage::Copy);
536
event->setDefaultHandled();
539
#endif // QT_NO_SHORTCUT
542
void EditorClientQt::handleInputMethodKeydown(KeyboardEvent*)
546
EditorClientQt::EditorClientQt(QWebPage* page)
547
: m_page(page), m_editing(false), m_inUndoRedo(false)
551
void EditorClientQt::textFieldDidBeginEditing(Element*)
556
void EditorClientQt::textFieldDidEndEditing(Element*)
561
void EditorClientQt::textDidChangeInTextField(Element*)
565
bool EditorClientQt::doTextFieldCommandFromEvent(Element*, KeyboardEvent*)
570
void EditorClientQt::textWillBeDeletedInTextField(Element*)
574
void EditorClientQt::textDidChangeInTextArea(Element*)
578
void EditorClientQt::updateSpellingUIWithGrammarString(const String&, const GrammarDetail&)
583
void EditorClientQt::updateSpellingUIWithMisspelledWord(const String&)
588
void EditorClientQt::showSpellingUI(bool)
593
bool EditorClientQt::spellingUIIsShowing()
599
bool EditorClientQt::isEditing() const
604
void EditorClientQt::willSetInputMethodState()
608
void EditorClientQt::setInputMethodState(bool active)
610
QWebPageClient* webPageClient = m_page->d->client.get();
612
Qt::InputMethodHints hints;
614
HTMLInputElement* inputElement = 0;
615
Frame* frame = m_page->d->page->focusController()->focusedOrMainFrame();
616
if (frame && frame->document() && frame->document()->focusedNode())
617
if (frame->document()->focusedNode()->hasTagName(HTMLNames::inputTag))
618
inputElement = static_cast<HTMLInputElement*>(frame->document()->focusedNode());
621
// Set input method hints for "number", "tel", "email", "url" and "password" input elements.
622
if (inputElement->isTelephoneField())
623
hints |= Qt::ImhDialableCharactersOnly;
624
if (inputElement->isNumberField())
625
hints |= Qt::ImhDigitsOnly;
626
if (inputElement->isEmailField())
627
hints |= Qt::ImhEmailCharactersOnly;
628
if (inputElement->isURLField())
629
hints |= Qt::ImhUrlCharactersOnly;
630
// Setting the Qt::WA_InputMethodEnabled attribute true and Qt::ImhHiddenText flag
631
// for password fields. The Qt platform is responsible for determining which widget
632
// will receive input method events for password fields.
633
if (inputElement->isPasswordField()) {
635
hints |= Qt::ImhHiddenText;
639
webPageClient->setInputMethodHints(hints);
640
webPageClient->setInputMethodEnabled(active);
642
emit m_page->microFocusChanged();
645
bool EditorClientQt::supportsGlobalSelection()
647
#ifndef QT_NO_CLIPBOARD
648
return qApp->clipboard()->supportsSelection();