[Qt] Separate QWidget dependant code into separate WebKitWidgets static library
[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 "Editor.h"
36 #include "FocusController.h"
37 #include "Frame.h"
38 #include "HTMLElement.h"
39 #include "HTMLInputElement.h"
40 #include "HTMLNames.h"
41 #include "KeyboardEvent.h"
42 #include "NotImplemented.h"
43 #include "Page.h"
44 #include "Pasteboard.h"
45 #include "PlatformKeyboardEvent.h"
46 #include "QWebPageAdapter.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
55 #include <QClipboard>
56 #include <QKeyEvent>
57 #include <QKeySequence>
58 #include <stdio.h>
59 #include <wtf/OwnPtr.h>
60
61
62 static QString dumpPath(WebCore::Node *node)
63 {
64     QString str = node->nodeName();
65
66     WebCore::Node *parent = node->parentNode();
67     while (parent) {
68         str.append(QLatin1String(" > "));
69         str.append(parent->nodeName());
70         parent = parent->parentNode();
71     }
72     return str;
73 }
74
75 static QString dumpRange(WebCore::Range *range)
76 {
77     if (!range)
78         return QLatin1String("(null)");
79     WebCore::ExceptionCode code;
80
81     QString str = QString::fromLatin1("range from %1 of %2 to %3 of %4")
82         .arg(range->startOffset(code)).arg(dumpPath(range->startContainer(code)))
83         .arg(range->endOffset(code)).arg(dumpPath(range->endContainer(code)));
84
85     return str;
86 }
87
88
89 namespace WebCore {
90
91 bool EditorClientQt::dumpEditingCallbacks = false;
92 bool EditorClientQt::acceptsEditing = true;
93
94 using namespace HTMLNames;
95
96 bool EditorClientQt::shouldDeleteRange(Range* range)
97 {
98     if (dumpEditingCallbacks)
99         printf("EDITING DELEGATE: shouldDeleteDOMRange:%s\n", dumpRange(range).toUtf8().constData());
100
101     return true;
102 }
103
104 bool EditorClientQt::shouldShowDeleteInterface(HTMLElement* element)
105 {
106     if (QWebPageAdapter::drtRun)
107         return element->getAttribute(classAttr) == "needsDeletionUI";
108     return false;
109 }
110
111 bool EditorClientQt::isContinuousSpellCheckingEnabled()
112 {
113     return m_textCheckerClient.isContinousSpellCheckingEnabled();
114 }
115
116 bool EditorClientQt::isGrammarCheckingEnabled()
117 {
118     return m_textCheckerClient.isGrammarCheckingEnabled();
119 }
120
121 int EditorClientQt::spellCheckerDocumentTag()
122 {
123     return 0;
124 }
125
126 bool EditorClientQt::shouldBeginEditing(WebCore::Range* range)
127 {
128     if (dumpEditingCallbacks)
129         printf("EDITING DELEGATE: shouldBeginEditingInDOMRange:%s\n", dumpRange(range).toUtf8().constData());
130     return true;
131 }
132
133 bool EditorClientQt::shouldEndEditing(WebCore::Range* range)
134 {
135     if (dumpEditingCallbacks)
136         printf("EDITING DELEGATE: shouldEndEditingInDOMRange:%s\n", dumpRange(range).toUtf8().constData());
137     return true;
138 }
139
140 bool EditorClientQt::shouldInsertText(const String& string, Range* range, EditorInsertAction action)
141 {
142     if (dumpEditingCallbacks) {
143         static const char *insertactionstring[] = {
144             "WebViewInsertActionTyped",
145             "WebViewInsertActionPasted",
146             "WebViewInsertActionDropped",
147         };
148
149         printf("EDITING DELEGATE: shouldInsertText:%s replacingDOMRange:%s givenAction:%s\n",
150             QString(string).toUtf8().constData(), dumpRange(range).toUtf8().constData(), insertactionstring[action]);
151     }
152     return acceptsEditing;
153 }
154
155 bool EditorClientQt::shouldChangeSelectedRange(Range* currentRange, Range* proposedRange, EAffinity selectionAffinity, bool stillSelecting)
156 {
157     if (dumpEditingCallbacks) {
158         static const char *affinitystring[] = {
159             "NSSelectionAffinityUpstream",
160             "NSSelectionAffinityDownstream"
161         };
162         static const char *boolstring[] = {
163             "FALSE",
164             "TRUE"
165         };
166
167         printf("EDITING DELEGATE: shouldChangeSelectedDOMRange:%s toDOMRange:%s affinity:%s stillSelecting:%s\n",
168             dumpRange(currentRange).toUtf8().constData(),
169             dumpRange(proposedRange).toUtf8().constData(),
170             affinitystring[selectionAffinity], boolstring[stillSelecting]);
171     }
172     return acceptsEditing;
173 }
174
175 bool EditorClientQt::shouldApplyStyle(WebCore::StylePropertySet* style, WebCore::Range* range)
176 {
177     if (dumpEditingCallbacks)
178         printf("EDITING DELEGATE: shouldApplyStyle:%s toElementsInDOMRange:%s\n",
179             QString(style->asText()).toUtf8().constData(), dumpRange(range).toUtf8().constData());
180     return acceptsEditing;
181 }
182
183 bool EditorClientQt::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
184 {
185     notImplemented();
186     return true;
187 }
188
189 void EditorClientQt::didBeginEditing()
190 {
191     if (dumpEditingCallbacks)
192         printf("EDITING DELEGATE: webViewDidBeginEditing:WebViewDidBeginEditingNotification\n");
193     m_editing = true;
194 }
195
196 void EditorClientQt::respondToChangedContents()
197 {
198     if (dumpEditingCallbacks)
199         printf("EDITING DELEGATE: webViewDidChange:WebViewDidChangeNotification\n");
200
201     m_page->respondToChangedContents();
202 }
203
204 void EditorClientQt::respondToChangedSelection(Frame* frame)
205 {
206     if (dumpEditingCallbacks)
207         printf("EDITING DELEGATE: webViewDidChangeSelection:WebViewDidChangeSelectionNotification\n");
208 //     const Selection &selection = m_page->d->page->selection();
209 //     char buffer[1024];
210 //     selection.formatForDebugger(buffer, sizeof(buffer));
211 //     printf("%s\n", buffer);
212
213     if (supportsGlobalSelection() && frame->selection()->isRange()) {
214         bool oldSelectionMode = Pasteboard::generalPasteboard()->isSelectionMode();
215         Pasteboard::generalPasteboard()->setSelectionMode(true);
216         Pasteboard::generalPasteboard()->writeSelection(frame->selection()->toNormalizedRange().get(), frame->editor()->canSmartCopyOrDelete(), frame);
217         Pasteboard::generalPasteboard()->setSelectionMode(oldSelectionMode);
218     }
219
220     m_page->respondToChangedSelection();
221     if (!frame->editor()->ignoreCompositionSelectionChange())
222         emit m_page->microFocusChanged();
223 }
224
225 void EditorClientQt::didEndEditing()
226 {
227     if (dumpEditingCallbacks)
228         printf("EDITING DELEGATE: webViewDidEndEditing:WebViewDidEndEditingNotification\n");
229     m_editing = false;
230 }
231
232 void EditorClientQt::didWriteSelectionToPasteboard()
233 {
234 }
235
236 void EditorClientQt::didSetSelectionTypesForPasteboard()
237 {
238 }
239
240 bool EditorClientQt::selectWordBeforeMenuEvent()
241 {
242     notImplemented();
243     return false;
244 }
245
246 void EditorClientQt::registerUndoStep(WTF::PassRefPtr<WebCore::UndoStep> step)
247 {
248 #ifndef QT_NO_UNDOSTACK
249     Frame* frame = m_page->page->focusController()->focusedOrMainFrame();
250     if (m_inUndoRedo || (frame && !frame->editor()->lastEditCommand() /* HACK!! Don't recreate undos */))
251         return;
252     m_page->registerUndoStep(step);
253 #endif // QT_NO_UNDOSTACK
254 }
255
256 void EditorClientQt::registerRedoStep(WTF::PassRefPtr<WebCore::UndoStep>)
257 {
258 }
259
260 void EditorClientQt::clearUndoRedoOperations()
261 {
262 #ifndef QT_NO_UNDOSTACK
263     return m_page->clearUndoStack();
264 #endif
265 }
266
267 bool EditorClientQt::canCopyCut(WebCore::Frame*, bool defaultValue) const
268 {
269     return defaultValue;
270 }
271
272 bool EditorClientQt::canPaste(WebCore::Frame*, bool defaultValue) const
273 {
274     return defaultValue;
275 }
276
277 bool EditorClientQt::canUndo() const
278 {
279 #ifdef QT_NO_UNDOSTACK
280     return false;
281 #else
282     return m_page->canUndo();
283 #endif
284 }
285
286 bool EditorClientQt::canRedo() const
287 {
288 #ifdef QT_NO_UNDOSTACK
289     return false;
290 #else
291     return m_page->canRedo();
292 #endif
293 }
294
295 void EditorClientQt::undo()
296 {
297 #ifndef QT_NO_UNDOSTACK
298     m_inUndoRedo = true;
299     m_page->undo();
300     m_inUndoRedo = false;
301 #endif
302 }
303
304 void EditorClientQt::redo()
305 {
306 #ifndef QT_NO_UNDOSTACK
307     m_inUndoRedo = true;
308     m_page->redo();
309     m_inUndoRedo = false;
310 #endif
311 }
312
313 bool EditorClientQt::shouldInsertNode(Node* node, Range* range, EditorInsertAction action)
314 {
315     if (dumpEditingCallbacks) {
316         static const char *insertactionstring[] = {
317             "WebViewInsertActionTyped",
318             "WebViewInsertActionPasted",
319             "WebViewInsertActionDropped",
320         };
321
322         printf("EDITING DELEGATE: shouldInsertNode:%s replacingDOMRange:%s givenAction:%s\n", dumpPath(node).toUtf8().constData(),
323             dumpRange(range).toUtf8().constData(), insertactionstring[action]);
324     }
325     return acceptsEditing;
326 }
327
328 void EditorClientQt::pageDestroyed()
329 {
330     delete this;
331 }
332
333 bool EditorClientQt::smartInsertDeleteEnabled()
334 {
335     return m_smartInsertDeleteEnabled;
336 }
337
338 void EditorClientQt::toggleSmartInsertDelete()
339 {
340     bool current = m_smartInsertDeleteEnabled;
341     m_smartInsertDeleteEnabled = !current;
342 }
343
344 bool EditorClientQt::isSelectTrailingWhitespaceEnabled()
345 {
346     return m_selectTrailingWhitespaceEnabled;
347 }
348
349 void EditorClientQt::toggleContinuousSpellChecking()
350 {
351     m_textCheckerClient.toggleContinousSpellChecking();
352 }
353
354 void EditorClientQt::toggleGrammarChecking()
355 {
356     return m_textCheckerClient.toggleGrammarChecking();
357 }
358
359 static const unsigned CtrlKey = 1 << 0;
360 static const unsigned AltKey = 1 << 1;
361 static const unsigned ShiftKey = 1 << 2;
362
363 struct KeyDownEntry {
364     unsigned virtualKey;
365     unsigned modifiers;
366     const char* editorCommand;
367 };
368
369 // Handle here key down events that are needed for spatial navigation and caret browsing, or
370 // are not handled by QWebPage.
371 static const KeyDownEntry keyDownEntries[] = {
372     // Ones that do not have an associated QAction:
373     { VK_DELETE, 0,                  "DeleteForward"                     },
374     { VK_BACK,   ShiftKey,           "DeleteBackward"                    },
375     { VK_BACK,   0,                  "DeleteBackward"                    },
376     // Ones that need special handling for caret browsing:
377     { VK_PRIOR,  0,                  "MovePageUp"                        },
378     { VK_PRIOR,  ShiftKey,           "MovePageUpAndModifySelection"      },
379     { VK_NEXT,   0,                  "MovePageDown"                      },
380     { VK_NEXT,   ShiftKey,           "MovePageDownAndModifySelection"    },
381     // Ones that need special handling for spatial navigation:
382     { VK_LEFT,   0,                  "MoveLeft"                          },
383     { VK_RIGHT,  0,                  "MoveRight"                         },
384     { VK_UP,     0,                  "MoveUp"                            },
385     { VK_DOWN,   0,                  "MoveDown"                          },
386 };
387
388 const char* editorCommandForKeyDownEvent(const KeyboardEvent* event)
389 {
390     if (event->type() != eventNames().keydownEvent)
391         return "";
392
393     static HashMap<int, const char*> keyDownCommandsMap;
394     if (keyDownCommandsMap.isEmpty()) {
395
396         unsigned numEntries = sizeof(keyDownEntries) / sizeof((keyDownEntries)[0]);
397         for (unsigned i = 0; i < numEntries; i++)
398             keyDownCommandsMap.set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].editorCommand);
399     }
400
401     unsigned modifiers = 0;
402     if (event->shiftKey())
403         modifiers |= ShiftKey;
404     if (event->altKey())
405         modifiers |= AltKey;
406     if (event->ctrlKey())
407         modifiers |= CtrlKey;
408
409     int mapKey = modifiers << 16 | event->keyCode();
410     return mapKey ? keyDownCommandsMap.get(mapKey) : 0;
411 }
412
413 void EditorClientQt::handleKeyboardEvent(KeyboardEvent* event)
414 {
415     Frame* frame = m_page->page->focusController()->focusedOrMainFrame();
416     if (!frame)
417         return;
418
419     const PlatformKeyboardEvent* kevent = event->keyEvent();
420     if (!kevent || kevent->type() == PlatformEvent::KeyUp)
421         return;
422
423     Node* start = frame->selection()->start().containerNode();
424     if (!start)
425         return;
426
427     // FIXME: refactor all of this to use Actions or something like them
428     if (start->isContentEditable()) {
429         bool doSpatialNavigation = false;
430         if (isSpatialNavigationEnabled(frame)) {
431             if (!kevent->modifiers()) {
432                 switch (kevent->windowsVirtualKeyCode()) {
433                 case VK_LEFT:
434                 case VK_RIGHT:
435                 case VK_UP:
436                 case VK_DOWN:
437                     doSpatialNavigation = true;
438                 }
439             }
440         }
441
442 #ifndef QT_NO_SHORTCUT
443         const char* cmd = m_page->editorCommandForKeyEvent(kevent->qtEvent());
444         if (cmd && !doSpatialNavigation) {
445             // WebKit doesn't have enough information about mode to decide how commands that just insert text if executed via Editor should be treated,
446             // so we leave it upon WebCore to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated
447             // (e.g. Tab that inserts a Tab character, or Enter).
448             if (frame->editor()->command(cmd).isTextInsertion()
449                 && kevent->type() == PlatformEvent::RawKeyDown)
450                 return;
451
452             m_page->triggerActionForKeyEvent(kevent->qtEvent());
453             event->setDefaultHandled();
454             return;
455         }
456 #endif // QT_NO_SHORTCUT
457         {
458             String commandName = editorCommandForKeyDownEvent(event);
459             if (!commandName.isEmpty()) {
460                 if (frame->editor()->command(commandName).execute()) // Event handled.
461                     event->setDefaultHandled();
462                 return;
463             }
464
465             if (kevent->windowsVirtualKeyCode() == VK_TAB) {
466                 // Do not handle TAB text insertion here.
467                 return;
468             }
469
470             // Text insertion.
471             bool shouldInsertText = false;
472             if (kevent->type() != PlatformEvent::KeyDown && !kevent->text().isEmpty()) {
473
474                 if (kevent->ctrlKey()) {
475                     if (kevent->altKey())
476                         shouldInsertText = true;
477                 } else {
478 #ifndef Q_WS_MAC
479                 // We need to exclude checking for Alt because it is just a different Shift
480                 if (!kevent->altKey())
481 #endif
482                     shouldInsertText = true;
483
484                 }
485             }
486
487             if (shouldInsertText) {
488                 frame->editor()->insertText(kevent->text(), event);
489                 event->setDefaultHandled();
490                 return;
491             }
492         }
493
494         // Event not handled.
495         return;
496     }
497
498     // Non editable content.
499     if (m_page->page->settings()->caretBrowsingEnabled()) {
500         switch (kevent->windowsVirtualKeyCode()) {
501         case VK_LEFT:
502         case VK_RIGHT:
503         case VK_UP:
504         case VK_DOWN:
505         case VK_HOME:
506         case VK_END:
507             {
508 #ifndef QT_NO_SHORTCUT
509                 m_page->triggerActionForKeyEvent(kevent->qtEvent());
510                 event->setDefaultHandled();
511 #endif
512                 return;
513             }
514         case VK_PRIOR: // PageUp
515         case VK_NEXT: // PageDown
516             {
517                 String commandName = editorCommandForKeyDownEvent(event);
518                 ASSERT(!commandName.isEmpty());
519                 frame->editor()->command(commandName).execute();
520                 event->setDefaultHandled();
521                 return;
522             }
523         }
524     }
525
526 #ifndef QT_NO_SHORTCUT
527     if (kevent->qtEvent() == QKeySequence::Copy) {
528         m_page->triggerCopyAction();
529         event->setDefaultHandled();
530         return;
531     }
532 #endif // QT_NO_SHORTCUT
533 }
534
535 void EditorClientQt::handleInputMethodKeydown(KeyboardEvent*)
536 {
537 }
538
539 EditorClientQt::EditorClientQt(QWebPageAdapter* pageAdapter)
540     : m_page(pageAdapter)
541     , m_editing(false)
542     , m_inUndoRedo(false)
543     , m_smartInsertDeleteEnabled(true)
544     , m_selectTrailingWhitespaceEnabled(false)
545 {
546 }
547
548 void EditorClientQt::textFieldDidBeginEditing(Element*)
549 {
550     m_editing = true;
551 }
552
553 void EditorClientQt::textFieldDidEndEditing(Element*)
554 {
555     m_editing = false;
556 }
557
558 void EditorClientQt::textDidChangeInTextField(Element*)
559 {
560 }
561
562 bool EditorClientQt::doTextFieldCommandFromEvent(Element*, KeyboardEvent*)
563 {
564     return false;
565 }
566
567 void EditorClientQt::textWillBeDeletedInTextField(Element*)
568 {
569 }
570
571 void EditorClientQt::textDidChangeInTextArea(Element*)
572 {
573 }
574
575 void EditorClientQt::updateSpellingUIWithGrammarString(const String&, const GrammarDetail&)
576 {
577     notImplemented();
578 }
579
580 void EditorClientQt::updateSpellingUIWithMisspelledWord(const String&)
581 {
582     notImplemented();
583 }
584
585 void EditorClientQt::showSpellingUI(bool)
586 {
587     notImplemented();
588 }
589
590 bool EditorClientQt::spellingUIIsShowing()
591 {
592     notImplemented();
593     return false;
594 }
595
596 bool EditorClientQt::isEditing() const
597 {
598     return m_editing;
599 }
600
601 void EditorClientQt::willSetInputMethodState()
602 {
603 }
604
605 void EditorClientQt::setInputMethodState(bool active)
606 {
607     QWebPageClient* webPageClient = m_page->client.data();
608     if (webPageClient) {
609         Qt::InputMethodHints hints;
610
611         HTMLInputElement* inputElement = 0;
612         Frame* frame = m_page->page->focusController()->focusedOrMainFrame();
613         if (frame && frame->document() && frame->document()->focusedNode())
614             if (frame->document()->focusedNode()->hasTagName(HTMLNames::inputTag))
615                 inputElement = static_cast<HTMLInputElement*>(frame->document()->focusedNode());
616
617         if (inputElement) {
618             // Set input method hints for "number", "tel", "email", "url" and "password" input elements.
619             if (inputElement->isTelephoneField())
620                 hints |= Qt::ImhDialableCharactersOnly;
621             if (inputElement->isNumberField())
622                 hints |= Qt::ImhDigitsOnly;
623             if (inputElement->isEmailField())
624                 hints |= Qt::ImhEmailCharactersOnly;
625             if (inputElement->isURLField())
626                 hints |= Qt::ImhUrlCharactersOnly;
627             // Setting the Qt::WA_InputMethodEnabled attribute true and Qt::ImhHiddenText flag
628             // for password fields. The Qt platform is responsible for determining which widget
629             // will receive input method events for password fields.
630             if (inputElement->isPasswordField()) {
631                 active = true;
632                 hints |= Qt::ImhHiddenText;
633             }
634         }
635
636         webPageClient->setInputMethodHints(hints);
637         webPageClient->setInputMethodEnabled(active);
638     }
639     emit m_page->microFocusChanged();
640 }
641
642 bool EditorClientQt::supportsGlobalSelection()
643 {
644 #ifndef QT_NO_CLIPBOARD
645     return qApp->clipboard()->supportsSelection();
646 #else
647     return false;
648 #endif
649 }
650
651 }
652
653 // vim: ts=4 sw=4 et