| /* |
| * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ |
| * Copyright (C) 2009, 2010, 2011, 2012 Research In Motion Limited. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include "config.h" |
| #include "EditorClientBlackBerry.h" |
| |
| #include "AutofillManager.h" |
| #include "DOMSupport.h" |
| #include "DumpRenderTreeClient.h" |
| #include "EditCommand.h" |
| #include "Frame.h" |
| #include "HTMLInputElement.h" |
| #include "HTMLNames.h" |
| #include "InputHandler.h" |
| #include "KeyboardEvent.h" |
| #include "NotImplemented.h" |
| #include "Page.h" |
| #include "PlatformKeyboardEvent.h" |
| #include "SelectionHandler.h" |
| #include "Settings.h" |
| #include "SpellChecker.h" |
| #include "WebPage_p.h" |
| #include "WindowsKeyboardCodes.h" |
| |
| using namespace BlackBerry::WebKit; |
| |
| namespace WebCore { |
| |
| // Arbitrary depth limit for the undo stack, to keep it from using |
| // unbounded memory. This is the maximum number of distinct undoable |
| // actions -- unbroken stretches of typed characters are coalesced |
| // into a single action only when not interrupted by string replacements |
| // triggered by replaceText calls. |
| static const size_t maximumUndoStackDepth = 1000; |
| |
| EditorClientBlackBerry::EditorClientBlackBerry(WebPagePrivate* webPagePrivate) |
| : m_webPagePrivate(webPagePrivate) |
| , m_waitingForCursorFocus(false) |
| , m_spellCheckState(SpellCheckDefault) |
| , m_inRedo(false) |
| { |
| } |
| |
| void EditorClientBlackBerry::pageDestroyed() |
| { |
| delete this; |
| } |
| |
| bool EditorClientBlackBerry::shouldDeleteRange(Range* range) |
| { |
| if (m_webPagePrivate->m_dumpRenderTree) |
| return m_webPagePrivate->m_dumpRenderTree->shouldDeleteDOMRange(range); |
| return true; |
| } |
| |
| bool EditorClientBlackBerry::smartInsertDeleteEnabled() |
| { |
| Page* page = WebPagePrivate::core(m_webPagePrivate->m_webPage); |
| if (!page) |
| return false; |
| return page->settings()->smartInsertDeleteEnabled(); |
| } |
| |
| bool EditorClientBlackBerry::isSelectTrailingWhitespaceEnabled() |
| { |
| Page* page = WebPagePrivate::core(m_webPagePrivate->m_webPage); |
| if (!page) |
| return false; |
| return page->settings()->selectTrailingWhitespaceEnabled(); |
| } |
| |
| void EditorClientBlackBerry::enableSpellChecking(bool enable) |
| { |
| m_spellCheckState = enable ? SpellCheckDefault : SpellCheckOff; |
| } |
| |
| bool EditorClientBlackBerry::shouldSpellCheckFocusedField() |
| { |
| const Frame* frame = m_webPagePrivate->focusedOrMainFrame(); |
| if (!frame || !frame->document() || !frame->editor()) |
| return false; |
| |
| const Node* node = frame->document()->focusedNode(); |
| // NOTE: This logic is taken from EditorClientImpl::shouldSpellcheckByDefault |
| // If |node| is null, we default to allowing spellchecking. This is done in |
| // order to mitigate the issue when the user clicks outside the textbox, as a |
| // result of which |node| becomes null, resulting in all the spell check |
| // markers being deleted. Also, the Frame will decide not to do spellchecking |
| // if the user can't edit - so returning true here will not cause any problems |
| // to the Frame's behavior. |
| if (!node) |
| return true; |
| |
| // If the field does not support autocomplete, do not do spellchecking. |
| if (node->isElementNode()) { |
| const Element* element = toElement(node); |
| if (element->hasTagName(HTMLNames::inputTag) && !DOMSupport::elementSupportsAutocomplete(element)) |
| return false; |
| } |
| |
| // Check if the node disables spell checking directly. |
| return frame->editor().isSpellCheckingEnabledInFocusedNode(); |
| } |
| |
| bool EditorClientBlackBerry::isContinuousSpellCheckingEnabled() |
| { |
| if (m_spellCheckState == SpellCheckOff) |
| return false; |
| if (m_spellCheckState == SpellCheckOn) |
| return true; |
| return shouldSpellCheckFocusedField(); |
| } |
| |
| void EditorClientBlackBerry::toggleContinuousSpellChecking() |
| { |
| // Use the current state to determine how to toggle, if it hasn't |
| // been explicitly set, it will toggle based on the field type. |
| if (isContinuousSpellCheckingEnabled()) |
| m_spellCheckState = SpellCheckOff; |
| else |
| m_spellCheckState = SpellCheckOn; |
| } |
| |
| bool EditorClientBlackBerry::isGrammarCheckingEnabled() |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| void EditorClientBlackBerry::toggleGrammarChecking() |
| { |
| notImplemented(); |
| } |
| |
| int EditorClientBlackBerry::spellCheckerDocumentTag() |
| { |
| notImplemented(); |
| return 0; |
| } |
| |
| bool EditorClientBlackBerry::shouldBeginEditing(Range* range) |
| { |
| if (m_webPagePrivate->m_dumpRenderTree) |
| return m_webPagePrivate->m_dumpRenderTree->shouldBeginEditingInDOMRange(range); |
| |
| return true; |
| } |
| |
| bool EditorClientBlackBerry::shouldEndEditing(Range* range) |
| { |
| if (m_webPagePrivate->m_dumpRenderTree) |
| return m_webPagePrivate->m_dumpRenderTree->shouldEndEditingInDOMRange(range); |
| return true; |
| } |
| |
| bool EditorClientBlackBerry::shouldInsertNode(Node* node, Range* range, EditorInsertAction insertAction) |
| { |
| if (m_webPagePrivate->m_dumpRenderTree) |
| return m_webPagePrivate->m_dumpRenderTree->shouldInsertNode(node, range, static_cast<int>(insertAction)); |
| return true; |
| } |
| |
| bool EditorClientBlackBerry::shouldInsertText(const WTF::String& text, Range* range, EditorInsertAction insertAction) |
| { |
| if (m_webPagePrivate->m_dumpRenderTree) |
| return m_webPagePrivate->m_dumpRenderTree->shouldInsertText(text, range, static_cast<int>(insertAction)); |
| return true; |
| } |
| |
| bool EditorClientBlackBerry::shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity affinity, bool stillSelecting) |
| { |
| if (m_webPagePrivate->m_dumpRenderTree) |
| return m_webPagePrivate->m_dumpRenderTree->shouldChangeSelectedDOMRangeToDOMRangeAffinityStillSelecting(fromRange, toRange, static_cast<int>(affinity), stillSelecting); |
| |
| Frame* frame = m_webPagePrivate->focusedOrMainFrame(); |
| if (frame && frame->document()) { |
| if (Node* focusedNode = frame->document()->focusedNode()) { |
| if (focusedNode->hasTagName(HTMLNames::selectTag)) |
| return false; |
| if (focusedNode->isElementNode() && DOMSupport::isPopupInputField(toElement(focusedNode))) |
| return false; |
| } |
| |
| // Check if this change does not represent a focus change and input is active and if so ensure the keyboard is visible. |
| if (m_webPagePrivate->m_inputHandler->isInputMode() && fromRange && toRange && (fromRange->startContainer() == toRange->startContainer())) |
| m_webPagePrivate->m_inputHandler->notifyClientOfKeyboardVisibilityChange(true); |
| } |
| |
| return true; |
| } |
| |
| bool EditorClientBlackBerry::shouldApplyStyle(StylePropertySet*, Range*) |
| { |
| notImplemented(); |
| return true; |
| } |
| |
| bool EditorClientBlackBerry::shouldMoveRangeAfterDelete(Range*, Range*) |
| { |
| notImplemented(); |
| return true; |
| } |
| |
| void EditorClientBlackBerry::didBeginEditing() |
| { |
| if (m_webPagePrivate->m_dumpRenderTree) |
| m_webPagePrivate->m_dumpRenderTree->didBeginEditing(); |
| } |
| |
| void EditorClientBlackBerry::respondToChangedContents() |
| { |
| if (m_webPagePrivate->m_dumpRenderTree) |
| m_webPagePrivate->m_dumpRenderTree->didChange(); |
| } |
| |
| void EditorClientBlackBerry::respondToChangedSelection(Frame* frame) |
| { |
| if (m_waitingForCursorFocus) |
| m_waitingForCursorFocus = false; |
| else |
| m_webPagePrivate->selectionChanged(frame); |
| |
| if (m_webPagePrivate->m_dumpRenderTree) |
| m_webPagePrivate->m_dumpRenderTree->didChangeSelection(); |
| } |
| |
| void EditorClientBlackBerry::didEndEditing() |
| { |
| if (m_webPagePrivate->m_dumpRenderTree) |
| m_webPagePrivate->m_dumpRenderTree->didEndEditing(); |
| } |
| |
| void EditorClientBlackBerry::respondToSelectionAppearanceChange() |
| { |
| m_webPagePrivate->m_selectionHandler->selectionPositionChanged(); |
| } |
| |
| void EditorClientBlackBerry::didWriteSelectionToPasteboard() |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::willWriteSelectionToPasteboard(WebCore::Range*) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::getClientPasteboardDataForRange(WebCore::Range*, Vector<String>&, Vector<RefPtr<WebCore::SharedBuffer> >&) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::didSetSelectionTypesForPasteboard() |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::registerUndoStep(PassRefPtr<UndoStep> step) |
| { |
| // Remove the oldest item if we've reached the maximum capacity for the stack. |
| if (m_undoStack.size() == maximumUndoStackDepth) |
| m_undoStack.removeFirst(); |
| |
| if (!m_inRedo) |
| m_redoStack.clear(); |
| |
| m_undoStack.append(step); |
| } |
| |
| void EditorClientBlackBerry::registerRedoStep(PassRefPtr<UndoStep> step) |
| { |
| m_redoStack.append(step); |
| } |
| |
| void EditorClientBlackBerry::clearUndoRedoOperations() |
| { |
| m_undoStack.clear(); |
| m_redoStack.clear(); |
| } |
| |
| bool EditorClientBlackBerry::canUndo() const |
| { |
| return !m_undoStack.isEmpty(); |
| } |
| |
| bool EditorClientBlackBerry::canRedo() const |
| { |
| return !m_redoStack.isEmpty(); |
| } |
| |
| bool EditorClientBlackBerry::canCopyCut(Frame*, bool defaultValue) const |
| { |
| return defaultValue; |
| } |
| |
| bool EditorClientBlackBerry::canPaste(Frame*, bool defaultValue) const |
| { |
| return defaultValue; |
| } |
| |
| void EditorClientBlackBerry::undo() |
| { |
| if (canUndo()) { |
| EditCommandStack::iterator back = --m_undoStack.end(); |
| RefPtr<UndoStep> command(*back); |
| m_undoStack.remove(back); |
| |
| // Unapply will call us back to push this command onto the redo stack. |
| command->unapply(); |
| } |
| } |
| |
| void EditorClientBlackBerry::redo() |
| { |
| if (canRedo()) { |
| EditCommandStack::iterator back = --m_redoStack.end(); |
| RefPtr<UndoStep> command(*back); |
| m_redoStack.remove(back); |
| |
| ASSERT(!m_inRedo); |
| m_inRedo = true; |
| |
| // Reapply will call us back to push this command onto the undo stack. |
| command->reapply(); |
| m_inRedo = false; |
| } |
| } |
| |
| static const unsigned CtrlKey = 1 << 0; |
| static const unsigned AltKey = 1 << 1; |
| static const unsigned ShiftKey = 1 << 2; |
| |
| struct KeyDownEntry { |
| unsigned virtualKey; |
| unsigned modifiers; |
| const char* name; |
| }; |
| |
| struct KeyPressEntry { |
| unsigned charCode; |
| unsigned modifiers; |
| const char* name; |
| }; |
| |
| static const KeyDownEntry keyDownEntries[] = { |
| { VK_LEFT, 0, "MoveLeft" }, |
| { VK_LEFT, ShiftKey, "MoveLeftAndModifySelection" }, |
| { VK_LEFT, CtrlKey, "MoveWordLeft" }, |
| { VK_LEFT, CtrlKey | ShiftKey, "MoveWordLeftAndModifySelection" }, |
| { VK_RIGHT, 0, "MoveRight" }, |
| { VK_RIGHT, ShiftKey, "MoveRightAndModifySelection" }, |
| { VK_RIGHT, CtrlKey, "MoveWordRight" }, |
| { VK_RIGHT, CtrlKey | ShiftKey, "MoveWordRightAndModifySelection" }, |
| { VK_UP, 0, "MoveUp" }, |
| { VK_UP, ShiftKey, "MoveUpAndModifySelection" }, |
| { VK_DOWN, 0, "MoveDown" }, |
| { VK_DOWN, ShiftKey, "MoveDownAndModifySelection" }, |
| { VK_PRIOR, 0, "MovePageUp" }, |
| { VK_PRIOR, ShiftKey, "MovePageUpAndModifySelection" }, |
| { VK_NEXT, 0, "MovePageDown" }, |
| { VK_NEXT, ShiftKey, "MovePageDownAndModifySelection" }, |
| { VK_HOME, 0, "MoveToBeginningOfLine" }, |
| { VK_HOME, ShiftKey, "MoveToBeginningOfLineAndModifySelection" }, |
| { VK_HOME, CtrlKey, "MoveToBeginningOfDocument" }, |
| { VK_HOME, CtrlKey | ShiftKey, "MoveToBeginningOfDocumentAndModifySelection" }, |
| { VK_END, 0, "MoveToEndOfLine" }, |
| { VK_END, ShiftKey, "MoveToEndOfLineAndModifySelection" }, |
| { VK_END, CtrlKey, "MoveToEndOfDocument" }, |
| { VK_END, CtrlKey | ShiftKey, "MoveToEndOfDocumentAndModifySelection" }, |
| |
| { 'B', CtrlKey, "ToggleBold" }, |
| { 'I', CtrlKey, "ToggleItalic" }, |
| { 'U', CtrlKey, "ToggleUnderline" }, |
| |
| { VK_BACK, 0, "DeleteBackward" }, |
| { VK_BACK, ShiftKey, "DeleteBackward" }, |
| { VK_DELETE, 0, "DeleteForward" }, |
| { VK_BACK, CtrlKey, "DeleteWordBackward" }, |
| { VK_DELETE, CtrlKey, "DeleteWordForward" }, |
| |
| { 'C', CtrlKey, "Copy" }, |
| { 'V', CtrlKey, "Paste" }, |
| { 'V', CtrlKey | ShiftKey, "PasteAndMatchStyle" }, |
| { 'X', CtrlKey, "Cut" }, |
| { VK_INSERT, CtrlKey, "Copy" }, |
| { VK_DELETE, ShiftKey, "Cut" }, |
| { VK_INSERT, ShiftKey, "Paste" }, |
| |
| { 'A', CtrlKey, "SelectAll" }, |
| { 'Z', CtrlKey, "Undo" }, |
| { 'Z', CtrlKey | ShiftKey, "Redo" }, |
| { 'Y', CtrlKey, "Redo" }, |
| }; |
| |
| static const KeyPressEntry keyPressEntries[] = { |
| { '\t', 0, "InsertTab" }, |
| { '\t', ShiftKey, "InsertBacktab" }, |
| { '\r', 0, "InsertNewline" }, |
| { '\r', CtrlKey, "InsertNewline" }, |
| { '\r', AltKey, "InsertNewline" }, |
| { '\r', ShiftKey, "InsertLineBreak" }, |
| { '\r', AltKey | ShiftKey, "InsertNewline" }, |
| }; |
| |
| |
| const char* EditorClientBlackBerry::interpretKeyEvent(const KeyboardEvent* event) |
| { |
| ASSERT(event->type() == eventNames().keydownEvent || event->type() == eventNames().keypressEvent); |
| |
| static HashMap<int, const char*>* keyDownCommandsMap = 0; |
| static HashMap<int, const char*>* keyPressCommandsMap = 0; |
| |
| if (!keyDownCommandsMap) { |
| keyDownCommandsMap = new HashMap<int, const char*>; |
| keyPressCommandsMap = new HashMap<int, const char*>; |
| |
| for (size_t i = 0; i < WTF_ARRAY_LENGTH(keyDownEntries); ++i) |
| keyDownCommandsMap->set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name); |
| |
| for (size_t i = 0; i < WTF_ARRAY_LENGTH(keyPressEntries); ++i) |
| keyPressCommandsMap->set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name); |
| } |
| |
| unsigned modifiers = 0; |
| if (event->shiftKey()) |
| modifiers |= ShiftKey; |
| if (event->altKey()) |
| modifiers |= AltKey; |
| if (event->ctrlKey()) |
| modifiers |= CtrlKey; |
| |
| if (event->type() == eventNames().keydownEvent) { |
| int mapKey = modifiers << 16 | event->keyCode(); |
| return mapKey ? keyDownCommandsMap->get(mapKey) : 0; |
| } |
| |
| int mapKey = modifiers << 16 | event->charCode(); |
| return mapKey ? keyPressCommandsMap->get(mapKey) : 0; |
| } |
| |
| void EditorClientBlackBerry::handleKeyboardEvent(KeyboardEvent* event) |
| { |
| ASSERT(event); |
| |
| const PlatformKeyboardEvent* platformEvent = event->keyEvent(); |
| if (!platformEvent) |
| return; |
| |
| ASSERT(event->target()->toNode()); |
| Frame* frame = event->target()->toNode()->document()->frame(); |
| ASSERT(frame); |
| |
| String commandName = interpretKeyEvent(event); |
| |
| // Check to see we are not trying to insert text on key down. |
| ASSERT(!(event->type() == eventNames().keydownEvent && frame->editor().command(commandName).isTextInsertion())); |
| |
| if (!commandName.isEmpty()) { |
| // Hot key handling. Cancel processing mode. |
| if (commandName != "DeleteBackward") |
| m_webPagePrivate->m_inputHandler->setProcessingChange(false); |
| |
| if (frame->editor().command(commandName).execute()) |
| event->setDefaultHandled(); |
| return; |
| } |
| |
| if (!frame->editor().canEdit()) |
| return; |
| |
| // Text insertion commands should only be triggered from keypressEvent. |
| // There is an assert guaranteeing this in |
| // EventHandler::handleTextInputEvent. Note that windowsVirtualKeyCode |
| // is not set for keypressEvent: special keys should have been already |
| // handled in keydownEvent, which is called first. |
| if (event->type() != eventNames().keypressEvent) |
| return; |
| |
| // Don't insert null or control characters as they can result in unexpected behaviour. |
| if (event->charCode() < ' ') |
| return; |
| |
| // Don't insert anything if a modifier is pressed. |
| if (event->ctrlKey() || event->altKey()) |
| return; |
| |
| if (!platformEvent->text().isEmpty()) { |
| if (frame->editor().insertText(platformEvent->text(), event)) |
| event->setDefaultHandled(); |
| } |
| } |
| |
| void EditorClientBlackBerry::handleInputMethodKeydown(KeyboardEvent*) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::textFieldDidBeginEditing(Element*) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::textFieldDidEndEditing(Element* element) |
| { |
| if (m_webPagePrivate->m_webSettings->isFormAutofillEnabled()) { |
| if (HTMLInputElement* inputElement = element->toInputElement()) |
| m_webPagePrivate->m_autofillManager->textFieldDidEndEditing(inputElement); |
| } |
| } |
| |
| void EditorClientBlackBerry::textDidChangeInTextField(Element* element) |
| { |
| if (m_webPagePrivate->m_webSettings->isFormAutofillEnabled()) { |
| if (HTMLInputElement* inputElement = element->toInputElement()) |
| m_webPagePrivate->m_autofillManager->didChangeInTextField(inputElement); |
| } |
| } |
| |
| bool EditorClientBlackBerry::doTextFieldCommandFromEvent(Element*, KeyboardEvent*) |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| void EditorClientBlackBerry::textWillBeDeletedInTextField(Element*) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::textDidChangeInTextArea(Element*) |
| { |
| notImplemented(); |
| } |
| |
| bool EditorClientBlackBerry::shouldEraseMarkersAfterChangeSelection(TextCheckingType) const |
| { |
| return true; |
| } |
| |
| void EditorClientBlackBerry::ignoreWordInSpellDocument(const WTF::String&) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::learnWord(const WTF::String&) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::checkSpellingOfString(const UChar*, int, int*, int*) |
| { |
| notImplemented(); |
| } |
| |
| WTF::String EditorClientBlackBerry::getAutoCorrectSuggestionForMisspelledWord(const WTF::String&) |
| { |
| notImplemented(); |
| return WTF::String(); |
| } |
| |
| void EditorClientBlackBerry::checkGrammarOfString(const UChar*, int, WTF::Vector<GrammarDetail, 0u>&, int*, int*) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::requestCheckingOfString(PassRefPtr<TextCheckingRequest> textCheckingRequest) |
| { |
| RefPtr<SpellCheckRequest> spellCheckRequest = static_cast<SpellCheckRequest*>(textCheckingRequest.get()); |
| m_webPagePrivate->m_inputHandler->requestCheckingOfString(spellCheckRequest); |
| } |
| |
| void EditorClientBlackBerry::checkTextOfParagraph(const UChar*, int, TextCheckingTypeMask, Vector<TextCheckingResult>&) |
| { |
| notImplemented(); |
| } |
| |
| TextCheckerClient* EditorClientBlackBerry::textChecker() |
| { |
| return this; |
| } |
| |
| void EditorClientBlackBerry::updateSpellingUIWithGrammarString(const WTF::String&, const GrammarDetail&) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::updateSpellingUIWithMisspelledWord(const WTF::String&) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::showSpellingUI(bool) |
| { |
| notImplemented(); |
| } |
| |
| bool EditorClientBlackBerry::spellingUIIsShowing() |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| void EditorClientBlackBerry::getGuessesForWord(const WTF::String&, WTF::Vector<WTF::String, 0u>&) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::getGuessesForWord(const String&, const String&, Vector<String>&) |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::willSetInputMethodState() |
| { |
| notImplemented(); |
| } |
| |
| void EditorClientBlackBerry::setInputMethodState(bool) |
| { |
| m_webPagePrivate->m_inputHandler->focusedNodeChanged(); |
| } |
| |
| } // namespace WebCore |