#include "config.h"
#include "InputHandler.h"
#include "BackingStore.h"
#include "BackingStoreClient.h"
#include "CSSStyleDeclaration.h"
#include "CString.h"
#include "Chrome.h"
#include "DOMSupport.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "DocumentMarkerController.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameView.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HTMLOptGroupElement.h"
#include "HTMLOptionElement.h"
#include "HTMLSelectElement.h"
#include "HTMLTextAreaElement.h"
#include "NotImplemented.h"
#include "Page.h"
#include "PlatformKeyboardEvent.h"
#include "PluginView.h"
#include "Range.h"
#include "RenderLayer.h"
#include "RenderMenuList.h"
#include "RenderPart.h"
#include "RenderText.h"
#include "RenderTextControl.h"
#include "RenderWidget.h"
#include "ScopePointer.h"
#include "SelectionHandler.h"
#include "TextIterator.h"
#include "WebPageClient.h"
#include "WebPage_p.h"
#include "WebSettings.h"
#include <BlackBerryPlatformKeyboardEvent.h>
#include <BlackBerryPlatformMisc.h>
#include <BlackBerryPlatformSettings.h>
#include <imf/events.h>
#include <sys/keycodes.h>
#define ENABLE_INPUT_LOG 0
#define ENABLE_FOCUS_LOG 0
static const unsigned MaxLearnTextDataSize = 500;
using namespace BlackBerry::Platform;
using namespace WebCore;
#if ENABLE_INPUT_LOG
#define InputLog(severity, format, ...) logAlways(severity, format, ## __VA_ARGS__)
#else
#define InputLog(severity, format, ...)
#endif // ENABLE_INPUT_LOG
#if ENABLE_FOCUS_LOG
#define FocusLog(severity, format, ...) logAlways(severity, format, ## __VA_ARGS__)
#else
#define FocusLog(severity, format, ...)
#endif // ENABLE_FOCUS_LOG
namespace BlackBerry {
namespace WebKit {
class ProcessingChangeGuard {
public:
ProcessingChangeGuard(InputHandler* inputHandler)
: m_inputHandler(inputHandler)
, m_savedProcessingChange(false)
{
ASSERT(m_inputHandler);
m_savedProcessingChange = m_inputHandler->processingChange();
m_inputHandler->setProcessingChange(true);
}
~ProcessingChangeGuard()
{
m_inputHandler->setProcessingChange(m_savedProcessingChange);
}
private:
InputHandler* m_inputHandler;
bool m_savedProcessingChange;
};
InputHandler::InputHandler(WebPagePrivate* page)
: m_webPage(page)
, m_currentFocusElement(0)
, m_inputModeEnabled(false)
, m_processingChange(false)
, m_changingFocus(false)
, m_currentFocusElementType(TextEdit)
, m_currentFocusElementTextEditMask(DEFAULT_STYLE)
, m_composingTextStart(0)
, m_composingTextEnd(0)
, m_pendingKeyboardVisibilityChange(NoChange)
, m_delayKeyboardVisibilityChange(false)
{
}
InputHandler::~InputHandler()
{
}
static BlackBerryInputType convertInputType(const HTMLInputElement* inputElement)
{
if (inputElement->isPasswordField())
return InputTypePassword;
if (inputElement->isSearchField())
return InputTypeSearch;
if (inputElement->isEmailField())
return InputTypeEmail;
if (inputElement->isMonthControl())
return InputTypeMonth;
if (inputElement->isNumberField())
return InputTypeNumber;
if (inputElement->isTelephoneField())
return InputTypeTelephone;
if (inputElement->isURLField())
return InputTypeURL;
#if ENABLE(INPUT_TYPE_COLOR)
if (inputElement->isColorControl())
return InputTypeColor;
#endif
if (inputElement->isDateControl())
return InputTypeDate;
if (inputElement->isDateTimeControl())
return InputTypeDateTime;
if (inputElement->isDateTimeLocalControl())
return InputTypeDateTimeLocal;
if (inputElement->isTimeControl())
return InputTypeTime;
if (DOMSupport::elementIdOrNameIndicatesEmail(inputElement))
return InputTypeEmail;
if (DOMSupport::elementIdOrNameIndicatesUrl(inputElement))
return InputTypeURL;
if (DOMSupport::elementPatternIndicatesNumber(inputElement))
return InputTypeNumber;
if (DOMSupport::elementPatternIndicatesHexadecimal(inputElement))
return InputTypeHexadecimal;
return InputTypeText;
}
static int inputStyle(BlackBerryInputType type, const Element* element)
{
switch (type) {
case InputTypeEmail:
case InputTypeURL:
case InputTypeSearch:
case InputTypeText:
case InputTypeTextArea:
{
int imfMask = 0;
DOMSupport::AttributeState autoCompleteState = DOMSupport::elementSupportsAutocomplete(element);
if (autoCompleteState == DOMSupport::Off)
imfMask = NO_AUTO_TEXT | NO_PREDICTION;
else if (autoCompleteState != DOMSupport::On
&& DOMSupport::elementIdOrNameIndicatesNoAutocomplete(element))
imfMask = NO_AUTO_TEXT | NO_PREDICTION;
if (DOMSupport::elementSupportsAutocorrect(element) == DOMSupport::Off
|| (imfMask && DOMSupport::elementSupportsAutocorrect(element) == DOMSupport::Default))
imfMask |= NO_AUTO_CORRECTION;
if (imfMask)
return imfMask;
if ((type == InputTypeEmail || type == InputTypeURL) && autoCompleteState != DOMSupport::On)
return NO_AUTO_TEXT | NO_PREDICTION | NO_AUTO_CORRECTION;
break;
}
case InputTypeIsIndex:
case InputTypePassword:
case InputTypeNumber:
case InputTypeTelephone:
case InputTypeHexadecimal:
return NO_AUTO_TEXT | NO_PREDICTION | NO_AUTO_CORRECTION;
default:
break;
}
return DEFAULT_STYLE;
}
WTF::String InputHandler::elementText()
{
if (!isActiveTextEdit())
return WTF::String();
return DOMSupport::inputElementText(m_currentFocusElement.get());
}
BlackBerryInputType InputHandler::elementType(Element* element) const
{
if (element->formControlName() == HTMLNames::isindexTag)
return InputTypeIsIndex;
if (const HTMLInputElement* inputElement = static_cast<const HTMLInputElement*>(element->toInputElement()))
return convertInputType(inputElement);
if (element->hasTagName(HTMLNames::textareaTag))
return InputTypeTextArea;
return InputTypeTextArea;
}
void InputHandler::focusedNodeChanged()
{
ASSERT(m_webPage->m_page->focusController());
Frame* frame = m_webPage->m_page->focusController()->focusedOrMainFrame();
if (!frame || !frame->document())
return;
Node* node = frame->document()->focusedNode();
if (isActiveTextEdit() && m_currentFocusElement == node) {
notifyClientOfKeyboardVisibilityChange(true);
return;
}
if (node && node->isElementNode()) {
Element* element = static_cast<Element*>(node);
if (DOMSupport::isElementTypePlugin(element)) {
setPluginFocused(element);
return;
}
if (DOMSupport::isTextBasedContentEditableElement(element)) {
setElementFocused(element);
return;
}
}
if (isActiveTextEdit() && m_currentFocusElement->isContentEditable()) {
if (processingChange())
return;
Frame* frame = m_currentFocusElement->document()->frame();
ASSERT(frame);
if (frame == m_webPage->focusedOrMainFrame() && frame->selection()->start().anchorNode()
&& frame->selection()->start().anchorNode()->isContentEditable())
return;
}
setElementUnfocused();
}
void InputHandler::setPluginFocused(Element* element)
{
ASSERT(DOMSupport::isElementTypePlugin(element));
if (isActiveTextEdit())
setElementUnfocused();
m_currentFocusElementType = Plugin;
m_currentFocusElement = element;
}
static bool convertStringToWchar(const String& string, wchar_t* dest, int destCapacity, int* destLength)
{
ASSERT(dest);
if (!string.length()) {
destLength = 0;
return true;
}
UErrorCode ec = U_ZERO_ERROR;
u_strToUTF32(reinterpret_cast<UChar32*>(dest), destCapacity, destLength, string.characters(), string.length(), &ec);
if (ec) {
InputLog(LogLevelInfo, "InputHandler::convertStringToWchar Error converting string ec (%d).", ec);
destLength = 0;
return false;
}
return true;
}
static bool convertStringToWcharVector(const String& string, WTF::Vector<wchar_t>& wcharString)
{
ASSERT(wcharString.isEmpty());
int length = string.length();
if (!length)
return true;
if (!wcharString.tryReserveCapacity(length + 1)) {
logAlways(LogLevelWarn, "InputHandler::convertStringToWcharVector Cannot allocate memory for string.\n");
return false;
}
int destLength = 0;
if (!convertStringToWchar(string, wcharString.data(), length + 1, &destLength))
return false;
wcharString.resize(destLength);
return true;
}
static String convertSpannableStringToString(spannable_string_t* src)
{
if (!src || !src->str || !src->length)
return String();
WTF::Vector<UChar> dest;
int destCapacity = (src->length * 2) + 1;
if (!dest.tryReserveCapacity(destCapacity)) {
logAlways(LogLevelWarn, "InputHandler::convertSpannableStringToString Cannot allocate memory for string.\n");
return String();
}
int destLength = 0;
UErrorCode ec = U_ZERO_ERROR;
u_strFromUTF32(dest.data(), destCapacity, &destLength, reinterpret_cast<UChar32*>(src->str), src->length, &ec);
if (ec) {
InputLog(LogLevelInfo, "InputHandler::convertSpannableStringToString Error converting string ec (%d).", ec);
return String();
}
dest.resize(destLength);
return String(dest.data(), destLength);
}
void InputHandler::sendLearnTextDetails(const WTF::String& string)
{
Vector<wchar_t> wcharString;
if (!convertStringToWcharVector(string, wcharString) || wcharString.isEmpty())
return;
m_webPage->m_client->inputLearnText(wcharString.data(), wcharString.size());
}
void InputHandler::learnText()
{
if (!isActiveTextEdit())
return;
if (m_currentFocusElementTextEditMask & NO_PREDICTION || m_currentFocusElementTextEditMask & NO_AUTO_TEXT)
return;
String textInField(elementText());
textInField = textInField.substring(std::max(0, static_cast<int>(textInField.length() - MaxLearnTextDataSize)), textInField.length());
textInField.remove(0, textInField.find(" "));
ASSERT(textInField.length() <= MaxLearnTextDataSize);
if (textInField.isEmpty())
return;
InputLog(LogLevelInfo, "InputHandler::learnText '%s'", textInField.latin1().data());
sendLearnTextDetails(textInField);
}
void InputHandler::setElementUnfocused(bool refocusOccuring)
{
if (isActiveTextEdit()) {
FocusLog(LogLevelInfo, "InputHandler::setElementUnfocused");
learnText();
finishComposition();
if (!refocusOccuring)
notifyClientOfKeyboardVisibilityChange(false);
m_webPage->m_client->inputFocusLost();
m_webPage->m_selectionHandler->selectionPositionChanged();
if (!m_currentFocusElement->document()->frame()->selection()->isFocused())
m_currentFocusElement->document()->frame()->selection()->setFocused(true);
}
m_currentFocusElement = 0;
m_currentFocusElementType = TextEdit;
}
void InputHandler::enableInputMode(bool inputModeAllowed)
{
FocusLog(LogLevelInfo, "InputHandler::enableInputMode '%s', override is '%s'"
, inputModeAllowed ? "true" : "false"
, m_webPage->m_dumpRenderTree || Platform::Settings::get()->alwaysShowKeyboardOnFocus() ? "true" : "false");
m_inputModeEnabled = inputModeAllowed;
if (m_webPage->m_dumpRenderTree || Platform::Settings::get()->alwaysShowKeyboardOnFocus())
m_inputModeEnabled = true;
if (m_inputModeEnabled && isActiveTextEdit() && !m_currentFocusElement->document()->frame()->selection()->isFocused())
m_currentFocusElement->document()->frame()->selection()->setFocused(true);
}
void InputHandler::setElementFocused(Element* element)
{
ASSERT(DOMSupport::isTextBasedContentEditableElement(element));
ASSERT(element->document() && element->document()->frame());
if (element->document()->frame()->selection()->isFocused() != m_inputModeEnabled)
element->document()->frame()->selection()->setFocused(m_inputModeEnabled);
setElementUnfocused(true );
m_currentFocusElement = element;
m_currentFocusElementType = TextEdit;
BlackBerryInputType type = elementType(element);
m_currentFocusElementTextEditMask = inputStyle(type, element);
FocusLog(LogLevelInfo, "InputHandler::setElementFocused, Type=%d, Style=%d", type, m_currentFocusElementTextEditMask);
m_webPage->m_client->inputFocusGained(type, m_currentFocusElementTextEditMask);
handleInputLocaleChanged(m_webPage->m_webSettings->isWritingDirectionRTL());
if (!m_delayKeyboardVisibilityChange)
notifyClientOfKeyboardVisibilityChange(true);
}
bool InputHandler::openDatePopup(HTMLInputElement* element, BlackBerryInputType type)
{
if (!element || element->disabled() || !DOMSupport::isDateTimeInputField(element))
return false;
if (isActiveTextEdit())
clearCurrentFocusElement();
m_currentFocusElement = element;
m_currentFocusElementType = TextPopup;
WTF::String value = element->value();
WTF::String min = element->getAttribute(HTMLNames::minAttr).string();
WTF::String max = element->getAttribute(HTMLNames::maxAttr).string();
double step = element->getAttribute(HTMLNames::stepAttr).toDouble();
m_webPage->m_client->openDateTimePopup(type, value, min, max, step);
return true;
}
bool InputHandler::openColorPopup(HTMLInputElement* element)
{
if (!element || element->disabled() || !DOMSupport::isColorInputField(element))
return false;
if (isActiveTextEdit())
clearCurrentFocusElement();
m_currentFocusElement = element;
m_currentFocusElementType = TextPopup;
m_webPage->m_client->openColorPopup(element->value());
return true;
}
void InputHandler::setInputValue(const WTF::String& value)
{
if (!isActiveTextPopup())
return;
HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(m_currentFocusElement.get());
inputElement->setValue(value);
clearCurrentFocusElement();
}
void InputHandler::nodeTextChanged(const Node* node)
{
if (processingChange() || !node)
return;
if (node != m_currentFocusElement)
return;
InputLog(LogLevelInfo, "InputHandler::nodeTextChanged");
m_webPage->m_client->inputTextChanged();
removeAttributedTextMarker();
}
WebCore::IntRect InputHandler::boundingBoxForInputField()
{
if (!isActiveTextEdit())
return WebCore::IntRect();
if (!m_currentFocusElement->renderer())
return WebCore::IntRect();
return m_currentFocusElement->renderer()->absoluteBoundingBoxRect();
}
void InputHandler::ensureFocusTextElementVisible(CaretScrollType scrollType)
{
if (!m_inputModeEnabled || !m_currentFocusElement || !m_currentFocusElement->document())
return;
if (!Platform::Settings::get()->allowCenterScrollAdjustmentForInputFields() && scrollType != EdgeIfNeeded)
return;
Frame* elementFrame = m_currentFocusElement->document()->frame();
if (!elementFrame)
return;
Frame* mainFrame = m_webPage->mainFrame();
if (!mainFrame)
return;
FrameView* mainFrameView = mainFrame->view();
if (!mainFrameView)
return;
WebCore::IntRect selectionFocusRect;
switch (elementFrame->selection()->selectionType()) {
case VisibleSelection::CaretSelection:
selectionFocusRect = elementFrame->selection()->absoluteCaretBounds();
break;
case VisibleSelection::RangeSelection: {
Position selectionPosition;
if (m_webPage->m_selectionHandler->lastUpdatedEndPointIsValid())
selectionPosition = elementFrame->selection()->end();
else
selectionPosition = elementFrame->selection()->start();
selectionFocusRect = VisiblePosition(selectionPosition).absoluteCaretBounds();
break;
}
case VisibleSelection::NoSelection:
return;
}
int fontHeight = selectionFocusRect.height();
if (elementFrame != mainFrame) { selectionFocusRect.move(-elementFrame->view()->scrollPosition().x(), -elementFrame->view()->scrollPosition().y());
if (elementFrame->ownerRenderer()) {
WebCore::IntPoint frameOffset = elementFrame->ownerRenderer()->absoluteContentBox().location();
selectionFocusRect.move(frameOffset.x(), frameOffset.y());
}
}
Position start = elementFrame->selection()->start();
if (start.anchorNode() && start.anchorNode()->renderer()) {
if (RenderLayer* layer = start.anchorNode()->renderer()->enclosingLayer()) {
WebCore::IntRect actualScreenRect = WebCore::IntRect(mainFrameView->scrollPosition(), m_webPage->actualVisibleSize());
ScrollAlignment horizontalScrollAlignment = ScrollAlignment::alignToEdgeIfNeeded;
ScrollAlignment verticalScrollAlignment = ScrollAlignment::alignToEdgeIfNeeded;
if (scrollType != EdgeIfNeeded) {
if (RenderObject* focusedRenderer = m_currentFocusElement->renderer()) {
WebCore::IntRect nodeOutlineBounds = focusedRenderer->absoluteOutlineBounds();
WebCore::IntRect caretAtEdgeRect = rectForCaret(0);
int paddingX = abs(caretAtEdgeRect.x() - nodeOutlineBounds.x());
int paddingY = abs(caretAtEdgeRect.y() - nodeOutlineBounds.y());
if (selectionFocusRect.x() - paddingX == nodeOutlineBounds.x())
selectionFocusRect.setX(nodeOutlineBounds.x());
else if (selectionFocusRect.maxX() + paddingX == nodeOutlineBounds.maxX())
selectionFocusRect.setX(nodeOutlineBounds.maxX() - selectionFocusRect.width());
if (selectionFocusRect.y() - paddingY == nodeOutlineBounds.y())
selectionFocusRect.setY(nodeOutlineBounds.y() - selectionFocusRect.height());
else if (selectionFocusRect.maxY() + paddingY == nodeOutlineBounds.maxY())
selectionFocusRect.setY(nodeOutlineBounds.maxY() - selectionFocusRect.height());
if (selectionFocusRect.x() - caretAtEdgeRect.x() < actualScreenRect.width() / 2)
selectionFocusRect.setX(nodeOutlineBounds.x());
else
horizontalScrollAlignment = ScrollAlignment::alignCenterIfNeeded;
}
verticalScrollAlignment = (scrollType == CenterAlways) ? ScrollAlignment::alignCenterAlways : ScrollAlignment::alignCenterIfNeeded;
}
selectionFocusRect.inflate(40 );
WebCore::IntRect revealRect = layer->getRectToExpose(actualScreenRect, selectionFocusRect,
horizontalScrollAlignment,
verticalScrollAlignment);
mainFrameView->setConstrainsScrollingToContentEdge(false);
WebCore::IntPoint scrollLocation = revealRect.location();
scrollLocation.clampNegativeToZero();
WebCore::IntPoint maximumScrollPosition = WebCore::IntPoint(mainFrameView->contentsWidth() - actualScreenRect.width(), mainFrameView->contentsHeight() - actualScreenRect.height());
scrollLocation = scrollLocation.shrunkTo(maximumScrollPosition);
mainFrameView->setScrollPosition(scrollLocation);
mainFrameView->setConstrainsScrollingToContentEdge(true);
}
}
static const int s_minimumTextHeightInPixels = 6;
if (fontHeight && fontHeight < s_minimumTextHeightInPixels)
m_webPage->zoomAboutPoint(s_minimumTextHeightInPixels / fontHeight, m_webPage->centerOfVisibleContentsRect());
}
void InputHandler::ensureFocusPluginElementVisible()
{
if (!isActivePlugin() || !m_currentFocusElement->document())
return;
Frame* elementFrame = m_currentFocusElement->document()->frame();
if (!elementFrame)
return;
Frame* mainFrame = m_webPage->mainFrame();
if (!mainFrame)
return;
FrameView* mainFrameView = mainFrame->view();
if (!mainFrameView)
return;
WebCore::IntRect selectionFocusRect;
RenderWidget* renderWidget = static_cast<RenderWidget*>(m_currentFocusElement->renderer());
if (renderWidget) {
PluginView* pluginView = static_cast<PluginView*>(renderWidget->widget());
if (pluginView)
selectionFocusRect = pluginView->ensureVisibleRect();
}
if (selectionFocusRect.isEmpty())
return;
if (elementFrame != mainFrame) { selectionFocusRect.move(-elementFrame->view()->scrollPosition().x(), -elementFrame->view()->scrollPosition().y());
if (elementFrame->ownerRenderer()) {
WebCore::IntPoint frameOffset = elementFrame->ownerRenderer()->absoluteContentBox().location();
selectionFocusRect.move(frameOffset.x(), frameOffset.y());
}
}
WebCore::IntRect actualScreenRect = WebCore::IntRect(mainFrameView->scrollPosition(), m_webPage->actualVisibleSize());
if (actualScreenRect.contains(selectionFocusRect))
return;
WebCore::IntPoint pos(selectionFocusRect.center().x() - actualScreenRect.width() / 2,
selectionFocusRect.center().y() - actualScreenRect.height() / 2);
mainFrameView->setScrollPosition(pos);
}
void InputHandler::ensureFocusElementVisible(bool centerInView)
{
if (isActivePlugin())
ensureFocusPluginElementVisible();
else
ensureFocusTextElementVisible(centerInView ? CenterAlways : CenterIfNeeded);
}
void InputHandler::frameUnloaded(const Frame* frame)
{
if (!isActiveTextEdit())
return;
if (m_currentFocusElement->document()->frame() != frame)
return;
FocusLog(LogLevelInfo, "InputHandler::frameUnloaded");
setElementUnfocused(false );
}
void InputHandler::setDelayKeyboardVisibilityChange(bool value)
{
m_delayKeyboardVisibilityChange = value;
m_pendingKeyboardVisibilityChange = NoChange;
}
void InputHandler::processPendingKeyboardVisibilityChange()
{
if (!m_delayKeyboardVisibilityChange) {
ASSERT(m_pendingKeyboardVisibilityChange == NoChange);
return;
}
m_delayKeyboardVisibilityChange = false;
if (m_pendingKeyboardVisibilityChange == NoChange)
return;
notifyClientOfKeyboardVisibilityChange(m_pendingKeyboardVisibilityChange == Visible);
m_pendingKeyboardVisibilityChange = NoChange;
}
void InputHandler::notifyClientOfKeyboardVisibilityChange(bool visible)
{
if (!m_inputModeEnabled && visible)
return;
if (!m_delayKeyboardVisibilityChange) {
m_webPage->showVirtualKeyboard(visible);
return;
}
m_pendingKeyboardVisibilityChange = visible ? Visible : NotVisible;
}
bool InputHandler::selectionAtStartOfElement()
{
if (!isActiveTextEdit())
return false;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
if (!selectionStart())
return true;
return false;
}
bool InputHandler::selectionAtEndOfElement()
{
if (!isActiveTextEdit())
return false;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
return selectionStart() == static_cast<int>(elementText().length());
}
int InputHandler::selectionStart() const
{
return selectionPosition(true);
}
int InputHandler::selectionEnd() const
{
return selectionPosition(false);
}
int InputHandler::selectionPosition(bool start) const
{
if (!m_currentFocusElement->document() || !m_currentFocusElement->document()->frame())
return 0;
if (HTMLTextFormControlElement* controlElement = DOMSupport::toTextControlElement(m_currentFocusElement.get()))
return start ? controlElement->selectionStart() : controlElement->selectionEnd();
FrameSelection caretSelection;
caretSelection.setSelection(m_currentFocusElement->document()->frame()->selection()->selection());
RefPtr<Range> rangeSelection = caretSelection.selection().toNormalizedRange();
if (!rangeSelection)
return 0;
int selectionPointInNode = start ? rangeSelection->startOffset() : rangeSelection->endOffset();
Node* containerNode = start ? rangeSelection->startContainer() : rangeSelection->endContainer();
ExceptionCode ec;
RefPtr<Range> rangeForNode = rangeOfContents(m_currentFocusElement.get());
rangeForNode->setEnd(containerNode, selectionPointInNode, ec);
ASSERT(!ec);
return TextIterator::rangeLength(rangeForNode.get());
}
void InputHandler::selectionChanged()
{
if (!m_webPage->m_mainFrame)
return;
if (!isActiveTextEdit())
return;
if (processingChange())
return;
ensureFocusTextElementVisible(EdgeIfNeeded);
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
int newSelectionStart = selectionStart();
int newSelectionEnd = selectionEnd();
InputLog(LogLevelInfo, "InputHandler::selectionChanged selectionStart=%u, selectionEnd=%u", newSelectionStart, newSelectionEnd);
m_webPage->m_client->inputSelectionChanged(newSelectionStart, newSelectionEnd);
removeAttributedTextMarker();
}
bool InputHandler::setCursorPosition(int location)
{
return setSelection(location, location);
}
bool InputHandler::setSelection(int start, int end, bool changeIsPartOfComposition)
{
if (!isActiveTextEdit())
return false;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
ProcessingChangeGuard guard(this);
VisibleSelection newSelection = DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), start, end);
m_currentFocusElement->document()->frame()->selection()->setSelection(newSelection, changeIsPartOfComposition ? 0 : FrameSelection::CloseTyping | FrameSelection::ClearTypingStyle);
InputLog(LogLevelInfo, "InputHandler::setSelection selectionStart=%u, selectionEnd=%u", start, end);
return start == selectionStart() && end == selectionEnd();
}
WebCore::IntRect InputHandler::rectForCaret(int index)
{
if (!isActiveTextEdit())
return WebCore::IntRect();
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
if (index < 0 || index > static_cast<int>(elementText().length())) {
return WebCore::IntRect();
}
FrameSelection caretSelection;
caretSelection.setSelection(DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), index, index).visibleStart());
caretSelection.modify(FrameSelection::AlterationExtend, DirectionForward, CharacterGranularity);
return caretSelection.selection().visibleStart().absoluteCaretBounds();
}
void InputHandler::cancelSelection()
{
if (!isActiveTextEdit())
return;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
int selectionStartPosition = selectionStart();
ProcessingChangeGuard guard(this);
setCursorPosition(selectionStartPosition);
}
bool InputHandler::handleKeyboardInput(const Platform::KeyboardEvent& keyboardEvent, bool changeIsPartOfComposition)
{
InputLog(LogLevelInfo, "InputHandler::handleKeyboardInput received character=%lc, type=%d", keyboardEvent.character(), keyboardEvent.type());
enableInputMode();
if (!changeIsPartOfComposition && compositionActive())
return false;
ProcessingChangeGuard guard(this);
unsigned adjustedModifiers = keyboardEvent.modifiers();
if (WTF::isASCIIUpper(keyboardEvent.character()))
adjustedModifiers |= KEYMOD_SHIFT;
ASSERT(m_webPage->m_page->focusController());
bool keyboardEventHandled = false;
if (Frame* focusedFrame = m_webPage->m_page->focusController()->focusedFrame()) {
bool isKeyChar = keyboardEvent.type() == Platform::KeyboardEvent::KeyChar;
Platform::KeyboardEvent::Type type = keyboardEvent.type();
if (isKeyChar)
type = Platform::KeyboardEvent::KeyDown;
Platform::KeyboardEvent adjustedKeyboardEvent(keyboardEvent.character(), type, adjustedModifiers);
keyboardEventHandled = focusedFrame->eventHandler()->keyEvent(PlatformKeyboardEvent(adjustedKeyboardEvent));
if (isKeyChar) {
type = Platform::KeyboardEvent::KeyUp;
adjustedKeyboardEvent = Platform::KeyboardEvent(keyboardEvent.character(), type, adjustedModifiers);
keyboardEventHandled = focusedFrame->eventHandler()->keyEvent(PlatformKeyboardEvent(adjustedKeyboardEvent)) || keyboardEventHandled;
}
if (!changeIsPartOfComposition && type == Platform::KeyboardEvent::KeyUp)
ensureFocusTextElementVisible(EdgeIfNeeded);
}
return keyboardEventHandled;
}
bool InputHandler::deleteSelection()
{
if (!isActiveTextEdit())
return false;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
Frame* frame = m_currentFocusElement->document()->frame();
if (frame->selection()->selectionType() != VisibleSelection::RangeSelection)
return false;
ASSERT(frame->editor());
return frame->editor()->command("DeleteBackward").execute();
}
void InputHandler::insertText(const WTF::String& string)
{
if (!isActiveTextEdit())
return;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame() && m_currentFocusElement->document()->frame()->editor());
Editor* editor = m_currentFocusElement->document()->frame()->editor();
editor->command("InsertText").execute(string);
}
void InputHandler::clearField()
{
if (!isActiveTextEdit())
return;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame() && m_currentFocusElement->document()->frame()->editor());
Editor* editor = m_currentFocusElement->document()->frame()->editor();
editor->command("SelectAll").execute();
editor->command("DeleteBackward").execute();
}
bool InputHandler::executeTextEditCommand(const WTF::String& commandName)
{
ASSERT(m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->editor());
Editor* editor = m_webPage->focusedOrMainFrame()->editor();
return editor->command(commandName).execute();
}
void InputHandler::cut()
{
executeTextEditCommand("Cut");
}
void InputHandler::copy()
{
executeTextEditCommand("Copy");
}
void InputHandler::paste()
{
executeTextEditCommand("Paste");
}
void InputHandler::addAttributedTextMarker(int start, int end, const AttributeTextStyle& style)
{
if ((end - start) < 1 || end > static_cast<int>(elementText().length()))
return;
RefPtr<Range> markerRange = DOMSupport::visibleSelectionForRangeInputElement(m_currentFocusElement.get(), start, end).toNormalizedRange();
m_currentFocusElement->document()->markers()->addMarker(markerRange.get(), DocumentMarker::AttributeText, WTF::String("Input Marker"), style);
}
void InputHandler::removeAttributedTextMarker()
{
if (m_currentFocusElement && m_currentFocusElement->document())
m_currentFocusElement->document()->markers()->removeMarkers(DocumentMarker::AttributeText);
m_composingTextStart = 0;
m_composingTextEnd = 0;
}
void InputHandler::handleInputLocaleChanged(bool isRTL)
{
if (!isActiveTextEdit())
return;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
RenderObject* renderer = m_currentFocusElement->renderer();
if (!renderer)
return;
Editor* editor = m_currentFocusElement->document()->frame()->editor();
ASSERT(editor);
if ((renderer->style()->direction() == RTL) != isRTL)
editor->setBaseWritingDirection(isRTL ? RightToLeftWritingDirection : LeftToRightWritingDirection);
}
void InputHandler::clearCurrentFocusElement()
{
if (m_currentFocusElement)
m_currentFocusElement->blur();
}
bool InputHandler::willOpenPopupForNode(Node* node)
{
if (!node)
return false;
ASSERT(!node->isInShadowTree());
if (node->hasTagName(HTMLNames::selectTag) || node->hasTagName(HTMLNames::optionTag)) {
return true;
}
if (node->isElementNode()) {
Element* element = static_cast<Element*>(node);
if (DOMSupport::isPopupInputField(element))
return true;
}
return false;
}
bool InputHandler::didNodeOpenPopup(Node* node)
{
if (!node)
return false;
ASSERT(!node->isInShadowTree());
if (node->hasTagName(HTMLNames::selectTag))
return openSelectPopup(static_cast<HTMLSelectElement*>(node));
if (node->hasTagName(HTMLNames::optionTag)) {
HTMLOptionElement* optionElement = static_cast<HTMLOptionElement*>(node);
return openSelectPopup(optionElement->ownerSelectElement());
}
if (HTMLInputElement* element = node->toInputElement()) {
if (DOMSupport::isDateTimeInputField(element))
return openDatePopup(element, elementType(element));
if (DOMSupport::isColorInputField(element))
return openColorPopup(element);
}
return false;
}
bool InputHandler::openSelectPopup(HTMLSelectElement* select)
{
if (!select || select->disabled())
return false;
if (isActiveTextEdit())
clearCurrentFocusElement();
m_currentFocusElement = select;
m_currentFocusElementType = SelectPopup;
const WTF::Vector<HTMLElement*>& listItems = select->listItems();
int size = listItems.size();
if (!size) {
ScopeArray<WebString> labels;
bool* enableds = 0;
int* itemTypes = 0;
bool* selecteds = 0;
m_webPage->m_client->openPopupList(false, size, labels, enableds, itemTypes, selecteds);
return true;
}
bool multiple = select->multiple();
ScopeArray<WebString> labels;
labels.reset(new WebString[size]);
bool* enableds = new bool[size];
int* itemTypes = new int[size];
bool* selecteds = new bool[size];
for (int i = 0; i < size; i++) {
if (listItems[i]->hasTagName(HTMLNames::optionTag)) {
HTMLOptionElement* option = static_cast<HTMLOptionElement*>(listItems[i]);
labels[i] = option->textIndentedToRespectGroupLabel();
enableds[i] = option->disabled() ? 0 : 1;
selecteds[i] = option->selected();
itemTypes[i] = TypeOption;
} else if (listItems[i]->hasTagName(HTMLNames::optgroupTag)) {
HTMLOptGroupElement* optGroup = static_cast<HTMLOptGroupElement*>(listItems[i]);
labels[i] = optGroup->groupLabelText();
enableds[i] = optGroup->disabled() ? 0 : 1;
selecteds[i] = false;
itemTypes[i] = TypeGroup;
} else if (listItems[i]->hasTagName(HTMLNames::hrTag)) {
enableds[i] = false;
selecteds[i] = false;
itemTypes[i] = TypeSeparator;
}
}
m_webPage->m_client->openPopupList(multiple, size, labels, enableds, itemTypes, selecteds);
return true;
}
void InputHandler::setPopupListIndex(int index)
{
if (index == -2) return clearCurrentFocusElement();
if (!isActiveSelectPopup())
return clearCurrentFocusElement();
RenderObject* renderer = m_currentFocusElement->renderer();
if (renderer && renderer->isMenuList()) {
RenderMenuList* renderMenu = toRenderMenuList(renderer);
renderMenu->hidePopup();
}
HTMLSelectElement* selectElement = static_cast<HTMLSelectElement*>(m_currentFocusElement.get());
int optionIndex = selectElement->listToOptionIndex(index);
selectElement->optionSelectedByUser(optionIndex, true , true );
clearCurrentFocusElement();
}
void InputHandler::setPopupListIndexes(int size, const bool* selecteds)
{
if (!isActiveSelectPopup())
return clearCurrentFocusElement();
if (size < 0)
return;
HTMLSelectElement* selectElement = static_cast<HTMLSelectElement*>(m_currentFocusElement.get());
const WTF::Vector<HTMLElement*>& items = selectElement->listItems();
if (items.size() != static_cast<unsigned int>(size))
return;
HTMLOptionElement* option;
for (int i = 0; i < size; i++) {
if (items[i]->hasTagName(HTMLNames::optionTag)) {
option = static_cast<HTMLOptionElement*>(items[i]);
option->setSelectedState(selecteds[i]);
}
}
selectElement->dispatchFormControlChangeEvent();
selectElement->renderer()->repaint();
clearCurrentFocusElement();
}
bool InputHandler::setBatchEditingActive(bool active)
{
if (!isActiveTextEdit())
return false;
ASSERT(m_currentFocusElement->document());
ASSERT(m_currentFocusElement->document()->frame());
BackingStoreClient* backingStoreClientForFrame = m_webPage->backingStoreClientForFrame(m_webPage->mainFrame());
ASSERT(backingStoreClientForFrame);
if (!active)
backingStoreClientForFrame->backingStore()->resumeScreenAndBackingStoreUpdates(BackingStore::RenderAndBlit);
else
backingStoreClientForFrame->backingStore()->suspendScreenAndBackingStoreUpdates();
return true;
}
int InputHandler::caretPosition() const
{
if (!isActiveTextEdit())
return -1;
return selectionStart();
}
int relativeLeftOffset(int caretPosition, int leftOffset)
{
ASSERT(caretPosition >= 0);
return std::max(0, caretPosition - leftOffset);
}
int relativeRightOffset(int caretPosition, unsigned totalLengthOfText, int rightOffset)
{
ASSERT(caretPosition >= 0);
ASSERT(totalLengthOfText < INT_MAX);
return std::min(caretPosition + rightOffset, static_cast<int>(totalLengthOfText));
}
bool InputHandler::deleteTextRelativeToCursor(int leftOffset, int rightOffset)
{
if (!isActiveTextEdit() || compositionActive())
return false;
ProcessingChangeGuard guard(this);
InputLog(LogLevelInfo, "InputHandler::deleteTextRelativeToCursor left %d right %d", leftOffset, rightOffset);
int caretOffset = caretPosition();
int start = relativeLeftOffset(caretOffset, leftOffset);
int end = relativeRightOffset(caretOffset, elementText().length(), rightOffset);
if (!deleteText(start, end))
return false;
ensureFocusTextElementVisible(EdgeIfNeeded);
return true;
}
bool InputHandler::deleteText(int start, int end)
{
if (!isActiveTextEdit())
return false;
ProcessingChangeGuard guard(this);
if (!setSelection(start, end, true ))
return false;
InputLog(LogLevelInfo, "InputHandler::deleteText start %d end %d", start, end);
return deleteSelection();
}
spannable_string_t* InputHandler::spannableTextInRange(int start, int end, int32_t flags)
{
if (!isActiveTextEdit())
return 0;
if (start == end)
return 0;
ASSERT(end > start);
int length = end - start;
WTF::String textString = elementText().substring(start, length);
spannable_string_t* pst = (spannable_string_t*)malloc(sizeof(spannable_string_t));
if (!pst) {
InputLog(LogLevelInfo, "InputHandler::spannableTextInRange error allocating spannable string.");
return 0;
}
pst->str = (wchar_t*)malloc(sizeof(wchar_t) * (length + 1));
if (!pst->str) {
InputLog(LogLevelInfo, "InputHandler::spannableTextInRange Cannot allocate memory for string.\n");
free(pst);
return 0;
}
int stringLength = 0;
if (!convertStringToWchar(textString, pst->str, length + 1, &stringLength)) {
free(pst->str);
free(pst);
return 0;
}
pst->length = stringLength;
pst->spans_count = 0;
pst->spans = 0;
return pst;
}
spannable_string_t* InputHandler::selectedText(int32_t flags)
{
if (!isActiveTextEdit())
return 0;
return spannableTextInRange(selectionStart(), selectionEnd(), flags);
}
spannable_string_t* InputHandler::textBeforeCursor(int32_t length, int32_t flags)
{
if (!isActiveTextEdit())
return 0;
int caretOffset = caretPosition();
int start = relativeLeftOffset(caretOffset, length);
int end = caretOffset;
return spannableTextInRange(start, end, flags);
}
spannable_string_t* InputHandler::textAfterCursor(int32_t length, int32_t flags)
{
if (!isActiveTextEdit())
return 0;
int caretOffset = caretPosition();
int start = caretOffset;
int end = relativeRightOffset(caretOffset, elementText().length(), length);
return spannableTextInRange(start, end, flags);
}
extracted_text_t* InputHandler::extractedTextRequest(extracted_text_request_t* request, int32_t flags)
{
if (!isActiveTextEdit())
return 0;
extracted_text_t* extractedText = (extracted_text_t *)malloc(sizeof(extracted_text_t));
extractedText->text = spannableTextInRange(0, elementText().length(), flags);
extractedText->partial_start_offset = 0;
extractedText->partial_end_offset = 0;
extractedText->start_offset = 0;
extractedText->selection_start = selectionStart() - extractedText->start_offset;
extractedText->selection_end = selectionEnd() - extractedText->start_offset;
bool selectionActive = extractedText->selection_start != extractedText->selection_end;
bool singleLine = m_currentFocusElement->hasTagName(HTMLNames::inputTag);
extractedText->flags = selectionActive & singleLine;
return extractedText;
}
static void addCompositionTextStyleToAttributeTextStyle(AttributeTextStyle& style)
{
style.setUnderline(AttributeTextStyle::StandardUnderline);
}
static void addActiveTextStyleToAttributeTextStyle(AttributeTextStyle& style)
{
style.setBackgroundColor(Color("blue"));
style.setTextColor(Color("white"));
}
static AttributeTextStyle compositionTextStyle()
{
AttributeTextStyle style;
addCompositionTextStyleToAttributeTextStyle(style);
return style;
}
static AttributeTextStyle textStyleFromMask(int64_t mask)
{
AttributeTextStyle style;
if (mask & COMPOSED_TEXT_ATTRIB)
addCompositionTextStyleToAttributeTextStyle(style);
if (mask & ACTIVE_REGION_ATTRIB)
addActiveTextStyleToAttributeTextStyle(style);
return style;
}
bool InputHandler::removeComposedText()
{
if (compositionActive()) {
if (!deleteText(m_composingTextStart, m_composingTextEnd)) {
return false;
}
removeAttributedTextMarker();
}
return true;
}
int32_t InputHandler::setComposingRegion(int32_t start, int32_t end)
{
if (!isActiveTextEdit())
return -1;
if (!removeComposedText()) {
return -1;
}
m_composingTextStart = start;
m_composingTextEnd = end;
if (compositionActive())
addAttributedTextMarker(start, end, compositionTextStyle());
InputLog(LogLevelInfo, "InputHandler::setComposingRegion start %d end %d", start, end);
return 0;
}
int32_t InputHandler::finishComposition()
{
if (!isActiveTextEdit())
return -1;
if (!compositionActive())
return 0;
removeAttributedTextMarker();
InputLog(LogLevelInfo, "InputHandler::finishComposition completed");
return 0;
}
span_t* InputHandler::firstSpanInString(spannable_string_t* spannableString, SpannableStringAttribute attrib)
{
span_t* span = spannableString->spans;
for (unsigned int i = 0; i < spannableString->spans_count; i++) {
if (span->attributes_mask & attrib)
return span;
span++;
}
return 0;
}
bool InputHandler::isTrailingSingleCharacter(span_t* span, unsigned stringLength, unsigned composingTextLength)
{
if (composingTextLength != stringLength - 1)
return false;
if (!span)
return false;
if (span->start == span->end) {
if (span->start == stringLength - 1)
return true;
}
return false;
}
bool InputHandler::setText(spannable_string_t* spannableString)
{
if (!isActiveTextEdit() || !spannableString)
return false;
ASSERT(m_currentFocusElement->document() && m_currentFocusElement->document()->frame());
Frame* frame = m_currentFocusElement->document()->frame();
Editor* editor = frame->editor();
ASSERT(editor);
m_webPage->m_selectionHandler->setSelectionActive(false);
String textToInsert = convertSpannableStringToString(spannableString);
int textLength = textToInsert.length();
InputLog(LogLevelInfo, "InputHandler::setText spannableString is '%s', of length %d \n", textToInsert.latin1().data(), textLength);
span_t* changedSpan = firstSpanInString(spannableString, CHANGED_ATTRIB);
int composingTextStart = m_composingTextStart;
int composingTextEnd = m_composingTextEnd;
int composingTextLength = compositionLength();
removeAttributedTextMarker();
if (isTrailingSingleCharacter(changedSpan, textLength, composingTextLength)) {
if (firstSpanInString(spannableString, COMPOSED_TEXT_ATTRIB)) {
InputLog(LogLevelInfo, "InputHandler::setText Single trailing character detected. Text is being composed. \n");
return editor->command("InsertText").execute(textToInsert.right(1));
}
InputLog(LogLevelInfo, "InputHandler::setText Single trailing character detected. Text is not being composed. \n");
return handleKeyboardInput(Platform::KeyboardEvent(textToInsert[textLength - 1], Platform::KeyboardEvent::KeyChar, 0), false );
}
if (!changedSpan) {
if (composingTextLength == textLength) {
InputLog(LogLevelInfo, "InputHandler::setText No spans have changed. New text is the same length as the old. Nothing to do. \n");
return true;
}
if (composingTextLength - textLength == 1) {
InputLog(LogLevelInfo, "InputHandler::setText No spans have changed. New text is one character shorter than the old. Treating as 'delete'. \n");
return editor->command("DeleteBackward").execute();
}
}
if (composingTextLength && !setSelection(composingTextStart, composingTextEnd, true ))
return false;
if (!textLength) {
if (selectionActive())
return editor->command("DeleteBackward").execute();
return true;
}
bool requiresSpaceKeyPress = false;
if (textLength > 0 && textToInsert[textLength - 1] == 32 ) {
requiresSpaceKeyPress = true;
textLength--;
textToInsert.remove(textLength, 1);
}
InputLog(LogLevelInfo, "InputHandler::setText Request being processed. Text before processing: '%s'", elementText().latin1().data());
if (textLength == 1 && !spannableString->spans_count) {
InputLog(LogLevelInfo, "InputHandler::setText Single character entry treated as key-press in the absense of spans. \n");
return handleKeyboardInput(Platform::KeyboardEvent(textToInsert[0], Platform::KeyboardEvent::KeyChar, 0), true );
}
if (!textToInsert.isEmpty() && !editor->command("InsertText").execute(textToInsert)) {
InputLog(LogLevelWarn, "InputHandler::setText Failed to insert text '%s'", textToInsert.latin1().data());
return false;
}
if (requiresSpaceKeyPress)
handleKeyboardInput(Platform::KeyboardEvent(32 , Platform::KeyboardEvent::KeyChar, 0), true );
InputLog(LogLevelInfo, "InputHandler::setText Request being processed. Text after processing '%s'", elementText().latin1().data());
return true;
}
bool InputHandler::setTextAttributes(int insertionPoint, spannable_string_t* spannableString)
{
span_t* span = spannableString->spans;
for (unsigned int i = 0; i < spannableString->spans_count; i++) {
unsigned int startPosition = insertionPoint + span->start;
unsigned int endPosition = insertionPoint + span->end + 1;
if (endPosition < startPosition || endPosition > elementText().length())
return false;
if (!span->attributes_mask)
continue;
InputLog(LogLevelInfo, "InputHandler::setTextAttributes adding marker %d to %d - %d", startPosition, endPosition, span->attributes_mask);
addAttributedTextMarker(startPosition, endPosition, textStyleFromMask(span->attributes_mask));
span++;
}
InputLog(LogLevelInfo, "InputHandler::setTextAttributes attribute count %d", spannableString->spans_count);
return true;
}
bool InputHandler::setRelativeCursorPosition(int insertionPoint, int relativeCursorPosition)
{
if (!isActiveTextEdit())
return false;
if (relativeCursorPosition == 1) {
m_currentFocusElement->document()->frame()->selection()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
return true;
}
int cursorPosition = 0;
if (relativeCursorPosition <= 0) {
cursorPosition = insertionPoint + relativeCursorPosition;
} else {
cursorPosition = caretPosition() + relativeCursorPosition - 1;
}
if (cursorPosition < 0 || cursorPosition > (int)elementText().length())
return false;
InputLog(LogLevelInfo, "InputHandler::setRelativeCursorPosition cursor position %d", cursorPosition);
return setCursorPosition(cursorPosition);
}
bool InputHandler::setSpannableTextAndRelativeCursor(spannable_string_t* spannableString, int relativeCursorPosition, bool markTextAsComposing)
{
InputLog(LogLevelInfo, "InputHandler::setSpannableTextAndRelativeCursor(%d, %d, %d)\n", spannableString->length, relativeCursorPosition, markTextAsComposing);
int insertionPoint = compositionActive() ? m_composingTextStart : selectionStart();
ProcessingChangeGuard guard(this);
if (!setText(spannableString))
return false;
if (!setTextAttributes(insertionPoint, spannableString))
return false;
if (!setRelativeCursorPosition(insertionPoint, relativeCursorPosition))
return false;
if (markTextAsComposing) {
m_composingTextStart = insertionPoint;
m_composingTextEnd = insertionPoint + spannableString->length;
}
ensureFocusTextElementVisible(EdgeIfNeeded);
return true;
}
int32_t InputHandler::setComposingText(spannable_string_t* spannableString, int32_t relativeCursorPosition)
{
if (!isActiveTextEdit())
return -1;
if (!spannableString)
return -1;
InputLog(LogLevelInfo, "InputHandler::setComposingText at relativeCursorPosition: %d", relativeCursorPosition);
enableInputMode();
return setSpannableTextAndRelativeCursor(spannableString, relativeCursorPosition, true ) ? 0 : -1;
}
int32_t InputHandler::commitText(spannable_string_t* spannableString, int32_t relativeCursorPosition)
{
if (!isActiveTextEdit())
return -1;
if (!spannableString)
return -1;
InputLog(LogLevelInfo, "InputHandler::commitText");
return setSpannableTextAndRelativeCursor(spannableString, relativeCursorPosition, false ) ? 0 : -1;
}
}
}