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