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