eb73c9e9d45516c1c977b4b3cb7c095cfd5ef5cd
[WebKit-https.git] / Source / WebKit / qt / WebCoreSupport / EditorClientQt.cpp
1 /*
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)
6  *
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
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.
17  *
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.
29  */
30
31 #include "config.h"
32 #include "EditorClientQt.h"
33
34 #include "Document.h"
35 #include "UndoStepQt.h"
36 #include "Editor.h"
37 #include "FocusController.h"
38 #include "Frame.h"
39 #include "HTMLElement.h"
40 #include "HTMLInputElement.h"
41 #include "HTMLNames.h"
42 #include "KeyboardEvent.h"
43 #include "NotImplemented.h"
44 #include "Page.h"
45 #include "Pasteboard.h"
46 #include "PlatformKeyboardEvent.h"
47 #include "QWebPageClient.h"
48 #include "Range.h"
49 #include "Settings.h"
50 #include "SpatialNavigation.h"
51 #include "StylePropertySet.h"
52 #include "WindowsKeyboardCodes.h"
53 #include "qguiapplication.h"
54 #include "qwebpage.h"
55 #include "qwebpage_p.h"
56
57 #include <QClipboard>
58 #include <QUndoStack>
59 #include <stdio.h>
60 #include <wtf/OwnPtr.h>
61
62
63 static QString dumpPath(WebCore::Node *node)
64 {
65     QString str = node->nodeName();
66
67     WebCore::Node *parent = node->parentNode();
68     while (parent) {
69         str.append(QLatin1String(" > "));
70         str.append(parent->nodeName());
71         parent = parent->parentNode();
72     }
73     return str;
74 }
75
76 static QString dumpRange(WebCore::Range *range)
77 {
78     if (!range)
79         return QLatin1String("(null)");
80     WebCore::ExceptionCode code;
81
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)));
85
86     return str;
87 }
88
89
90 namespace WebCore {
91
92 bool EditorClientQt::dumpEditingCallbacks = false;
93 bool EditorClientQt::acceptsEditing = true;
94
95 using namespace HTMLNames;
96
97 bool EditorClientQt::shouldDeleteRange(Range* range)
98 {
99     if (dumpEditingCallbacks)
100         printf("EDITING DELEGATE: shouldDeleteDOMRange:%s\n", dumpRange(range).toUtf8().constData());
101
102     return true;
103 }
104
105 bool EditorClientQt::shouldShowDeleteInterface(HTMLElement* element)
106 {
107     if (QWebPagePrivate::drtRun)
108         return element->getAttribute(classAttr) == "needsDeletionUI";
109     return false;
110 }
111
112 bool EditorClientQt::isContinuousSpellCheckingEnabled()
113 {
114     return m_textCheckerClient.isContinousSpellCheckingEnabled();
115 }
116
117 bool EditorClientQt::isGrammarCheckingEnabled()
118 {
119     return m_textCheckerClient.isGrammarCheckingEnabled();
120 }
121
122 int EditorClientQt::spellCheckerDocumentTag()
123 {
124     return 0;
125 }
126
127 bool EditorClientQt::shouldBeginEditing(WebCore::Range* range)
128 {
129     if (dumpEditingCallbacks)
130         printf("EDITING DELEGATE: shouldBeginEditingInDOMRange:%s\n", dumpRange(range).toUtf8().constData());
131     return true;
132 }
133
134 bool EditorClientQt::shouldEndEditing(WebCore::Range* range)
135 {
136     if (dumpEditingCallbacks)
137         printf("EDITING DELEGATE: shouldEndEditingInDOMRange:%s\n", dumpRange(range).toUtf8().constData());
138     return true;
139 }
140
141 bool EditorClientQt::shouldInsertText(const String& string, Range* range, EditorInsertAction action)
142 {
143     if (dumpEditingCallbacks) {
144         static const char *insertactionstring[] = {
145             "WebViewInsertActionTyped",
146             "WebViewInsertActionPasted",
147             "WebViewInsertActionDropped",
148         };
149
150         printf("EDITING DELEGATE: shouldInsertText:%s replacingDOMRange:%s givenAction:%s\n",
151                QString(string).toUtf8().constData(), dumpRange(range).toUtf8().constData(), insertactionstring[action]);
152     }
153     return acceptsEditing;
154 }
155
156 bool EditorClientQt::shouldChangeSelectedRange(Range* currentRange, Range* proposedRange, EAffinity selectionAffinity, bool stillSelecting)
157 {
158     if (dumpEditingCallbacks) {
159         static const char *affinitystring[] = {
160             "NSSelectionAffinityUpstream",
161             "NSSelectionAffinityDownstream"
162         };
163         static const char *boolstring[] = {
164             "FALSE",
165             "TRUE"
166         };
167
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]);
172     }
173     return acceptsEditing;
174 }
175
176 bool EditorClientQt::shouldApplyStyle(WebCore::StylePropertySet* style,
177                                       WebCore::Range* range)
178 {
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;
183 }
184
185 bool EditorClientQt::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
186 {
187     notImplemented();
188     return true;
189 }
190
191 void EditorClientQt::didBeginEditing()
192 {
193     if (dumpEditingCallbacks)
194         printf("EDITING DELEGATE: webViewDidBeginEditing:WebViewDidBeginEditingNotification\n");
195     m_editing = true;
196 }
197
198 void EditorClientQt::respondToChangedContents()
199 {
200     if (dumpEditingCallbacks)
201         printf("EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification\n");
202     m_page->d->updateEditorActions();
203
204     emit m_page->contentsChanged();
205 }
206
207 void EditorClientQt::respondToChangedSelection(Frame* frame)
208 {
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);
215
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);
221     }
222
223     m_page->d->updateEditorActions();
224     emit m_page->selectionChanged();
225     if (!frame->editor()->ignoreCompositionSelectionChange())
226         emit m_page->microFocusChanged();
227 }
228
229 void EditorClientQt::didEndEditing()
230 {
231     if (dumpEditingCallbacks)
232         printf("EDITING DELEGATE: webViewDidEndEditing:WebViewDidEndEditingNotification\n");
233     m_editing = false;
234 }
235
236 void EditorClientQt::didWriteSelectionToPasteboard()
237 {
238 }
239
240 void EditorClientQt::didSetSelectionTypesForPasteboard()
241 {
242 }
243
244 bool EditorClientQt::selectWordBeforeMenuEvent()
245 {
246     notImplemented();
247     return false;
248 }
249
250 void EditorClientQt::registerUndoStep(WTF::PassRefPtr<WebCore::UndoStep> step)
251 {
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 */))
255         return;
256     m_page->undoStack()->push(new UndoStepQt(step));
257 #endif // QT_NO_UNDOSTACK
258 }
259
260 void EditorClientQt::registerRedoStep(WTF::PassRefPtr<WebCore::UndoStep>)
261 {
262 }
263
264 void EditorClientQt::clearUndoRedoOperations()
265 {
266 #ifndef QT_NO_UNDOSTACK
267     return m_page->undoStack()->clear();
268 #endif
269 }
270
271 bool EditorClientQt::canCopyCut(WebCore::Frame*, bool defaultValue) const
272 {
273     return defaultValue;
274 }
275
276 bool EditorClientQt::canPaste(WebCore::Frame*, bool defaultValue) const
277 {
278     return defaultValue;
279 }
280
281 bool EditorClientQt::canUndo() const
282 {
283 #ifdef QT_NO_UNDOSTACK
284     return false;
285 #else
286     return m_page->undoStack()->canUndo();
287 #endif
288 }
289
290 bool EditorClientQt::canRedo() const
291 {
292 #ifdef QT_NO_UNDOSTACK
293     return false;
294 #else
295     return m_page->undoStack()->canRedo();
296 #endif
297 }
298
299 void EditorClientQt::undo()
300 {
301 #ifndef QT_NO_UNDOSTACK
302     m_inUndoRedo = true;
303     m_page->undoStack()->undo();
304     m_inUndoRedo = false;
305 #endif
306 }
307
308 void EditorClientQt::redo()
309 {
310 #ifndef QT_NO_UNDOSTACK
311     m_inUndoRedo = true;
312     m_page->undoStack()->redo();
313     m_inUndoRedo = false;
314 #endif
315 }
316
317 bool EditorClientQt::shouldInsertNode(Node* node, Range* range, EditorInsertAction action)
318 {
319     if (dumpEditingCallbacks) {
320         static const char *insertactionstring[] = {
321             "WebViewInsertActionTyped",
322             "WebViewInsertActionPasted",
323             "WebViewInsertActionDropped",
324         };
325
326         printf("EDITING DELEGATE: shouldInsertNode:%s replacingDOMRange:%s givenAction:%s\n", dumpPath(node).toUtf8().constData(),
327                dumpRange(range).toUtf8().constData(), insertactionstring[action]);
328     }
329     return acceptsEditing;
330 }
331
332 void EditorClientQt::pageDestroyed()
333 {
334     delete this;
335 }
336
337 bool EditorClientQt::smartInsertDeleteEnabled()
338 {
339     return m_page->d->smartInsertDeleteEnabled;
340 }
341
342 void EditorClientQt::toggleSmartInsertDelete()
343 {
344     bool current = m_page->d->smartInsertDeleteEnabled;
345     m_page->d->smartInsertDeleteEnabled = !current;
346 }
347
348 bool EditorClientQt::isSelectTrailingWhitespaceEnabled()
349 {
350     return m_page->d->selectTrailingWhitespaceEnabled;
351 }
352
353 void EditorClientQt::toggleContinuousSpellChecking()
354 {
355     m_textCheckerClient.toggleContinousSpellChecking();
356 }
357
358 void EditorClientQt::toggleGrammarChecking()
359 {
360     return m_textCheckerClient.toggleGrammarChecking();
361 }
362
363 static const unsigned CtrlKey = 1 << 0;
364 static const unsigned AltKey = 1 << 1;
365 static const unsigned ShiftKey = 1 << 2;
366
367 struct KeyDownEntry {
368     unsigned virtualKey;
369     unsigned modifiers;
370     const char* editorCommand;
371 };
372
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"                          },
390 };
391
392 const char* editorCommandForKeyDownEvent(const KeyboardEvent* event)
393 {
394     if (event->type() != eventNames().keydownEvent)
395         return "";
396
397     static HashMap<int, const char*> keyDownCommandsMap;
398     if (keyDownCommandsMap.isEmpty()) {
399
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);
403     }
404
405     unsigned modifiers = 0;
406     if (event->shiftKey())
407         modifiers |= ShiftKey;
408     if (event->altKey())
409         modifiers |= AltKey;
410     if (event->ctrlKey())
411         modifiers |= CtrlKey;
412
413     int mapKey = modifiers << 16 | event->keyCode();
414     return mapKey ? keyDownCommandsMap.get(mapKey) : 0;
415 }
416
417 void EditorClientQt::handleKeyboardEvent(KeyboardEvent* event)
418 {
419     Frame* frame = m_page->d->page->focusController()->focusedOrMainFrame();
420     if (!frame)
421         return;
422
423     const PlatformKeyboardEvent* kevent = event->keyEvent();
424     if (!kevent || kevent->type() == PlatformEvent::KeyUp)
425         return;
426
427     Node* start = frame->selection()->start().containerNode();
428     if (!start)
429         return;
430
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()) {
437                 case VK_LEFT:
438                 case VK_RIGHT:
439                 case VK_UP:
440                 case VK_DOWN:
441                     doSpatialNavigation = true;
442                 }
443             }
444         }
445
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)
455                 return;
456
457             m_page->triggerAction(action);
458             event->setDefaultHandled();
459             return;
460         } else 
461 #endif // QT_NO_SHORTCUT
462         {
463             String commandName = editorCommandForKeyDownEvent(event);
464             if (!commandName.isEmpty()) {
465                 if (frame->editor()->command(commandName).execute()) // Event handled.
466                     event->setDefaultHandled();
467                 return;
468             }
469
470             if (kevent->windowsVirtualKeyCode() == VK_TAB) {
471                 // Do not handle TAB text insertion here.
472                 return;
473             }
474
475             // Text insertion.
476             bool shouldInsertText = false;
477             if (kevent->type() != PlatformEvent::KeyDown && !kevent->text().isEmpty()) {
478
479                 if (kevent->ctrlKey()) {
480                     if (kevent->altKey())
481                         shouldInsertText = true;
482                 } else {
483 #ifndef Q_WS_MAC
484                 // We need to exclude checking for Alt because it is just a different Shift
485                 if (!kevent->altKey())
486 #endif
487                     shouldInsertText = true;
488
489                 }
490             }
491
492             if (shouldInsertText) {
493                 frame->editor()->insertText(kevent->text(), event);
494                 event->setDefaultHandled();
495                 return;
496             }
497         }
498
499         // Event not handled.
500         return;
501     }
502
503     // Non editable content.
504     if (m_page->handle()->page->settings()->caretBrowsingEnabled()) {
505         switch (kevent->windowsVirtualKeyCode()) {
506         case VK_LEFT:
507         case VK_RIGHT:
508         case VK_UP:
509         case VK_DOWN:
510         case VK_HOME:
511         case VK_END:
512             {
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();
518 #endif
519                 return;
520             }
521         case VK_PRIOR: // PageUp
522         case VK_NEXT:  // PageDown
523             {
524                 String commandName = editorCommandForKeyDownEvent(event);
525                 ASSERT(!commandName.isEmpty());
526                 frame->editor()->command(commandName).execute();
527                 event->setDefaultHandled();
528                 return;
529             }
530         }
531     }
532
533 #ifndef QT_NO_SHORTCUT
534     if (kevent->qtEvent() == QKeySequence::Copy) {
535         m_page->triggerAction(QWebPage::Copy);
536         event->setDefaultHandled();
537         return;
538     }
539 #endif // QT_NO_SHORTCUT
540 }
541
542 void EditorClientQt::handleInputMethodKeydown(KeyboardEvent*)
543 {
544 }
545
546 EditorClientQt::EditorClientQt(QWebPage* page)
547     : m_page(page), m_editing(false), m_inUndoRedo(false)
548 {
549 }
550
551 void EditorClientQt::textFieldDidBeginEditing(Element*)
552 {
553     m_editing = true;
554 }
555
556 void EditorClientQt::textFieldDidEndEditing(Element*)
557 {
558     m_editing = false;
559 }
560
561 void EditorClientQt::textDidChangeInTextField(Element*)
562 {
563 }
564
565 bool EditorClientQt::doTextFieldCommandFromEvent(Element*, KeyboardEvent*)
566 {
567     return false;
568 }
569
570 void EditorClientQt::textWillBeDeletedInTextField(Element*)
571 {
572 }
573
574 void EditorClientQt::textDidChangeInTextArea(Element*)
575 {
576 }
577
578 void EditorClientQt::updateSpellingUIWithGrammarString(const String&, const GrammarDetail&)
579 {
580     notImplemented();
581 }
582
583 void EditorClientQt::updateSpellingUIWithMisspelledWord(const String&)
584 {
585     notImplemented();
586 }
587
588 void EditorClientQt::showSpellingUI(bool)
589 {
590     notImplemented();
591 }
592
593 bool EditorClientQt::spellingUIIsShowing()
594 {
595     notImplemented();
596     return false;
597 }
598
599 bool EditorClientQt::isEditing() const
600 {
601     return m_editing;
602 }
603
604 void EditorClientQt::willSetInputMethodState()
605 {
606 }
607
608 void EditorClientQt::setInputMethodState(bool active)
609 {
610     QWebPageClient* webPageClient = m_page->d->client.get();
611     if (webPageClient) {
612         Qt::InputMethodHints hints;
613
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());
619
620         if (inputElement) {
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()) {
634                 active = true;
635                 hints |= Qt::ImhHiddenText;
636             }
637         }
638
639         webPageClient->setInputMethodHints(hints);
640         webPageClient->setInputMethodEnabled(active);
641     }
642     emit m_page->microFocusChanged();
643 }
644
645 bool EditorClientQt::supportsGlobalSelection()
646 {
647 #ifndef QT_NO_CLIPBOARD
648     return qApp->clipboard()->supportsSelection();
649 #else
650     return false;
651 #endif
652 }
653
654 }
655
656 // vim: ts=4 sw=4 et