2 * Copyright (C) 2012 Samsung Electronics
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
28 #if ENABLE(TIZEN_WEBKIT2_TEXT_SELECTION)
29 #include "TextSelection.h"
31 #include "EditorState.h"
32 #include "EwkViewImpl.h"
33 #include "NativeWebMouseEvent.h"
35 #include <Elementary.h>
37 using namespace WebCore;
41 TextSelection::TextSelection(EwkViewImpl* viewImpl)
42 : m_viewImpl(viewImpl)
43 , m_isTextSelectionDowned(false)
44 , m_isTextSelectionMode(false)
47 #if ENABLE(TIZEN_WEBKIT2_FOR_MOVING_TEXT_SELECTION_HANDLE_FROM_OSP)
50 , m_handleMovingDirection(HandleMovingDirectionNormal)
54 const Eina_List* defaultThemeList = elm_theme_list_get(0);
58 EINA_LIST_FOREACH(defaultThemeList, l, theme) {
59 char* themePath = elm_theme_list_item_path_get((const char*)theme, 0);
62 m_leftHandle = new TextSelectionHandle(m_viewImpl->view(), themePath, "elm/entry/selection/block_handle_left", true, this);
63 m_rightHandle = new TextSelectionHandle(m_viewImpl->view(), themePath, "elm/entry/selection/block_handle_right", false, this);
70 m_magnifier = new TextSelectionMagnifier(m_viewImpl);
72 evas_object_event_callback_add(m_viewImpl->view(), EVAS_CALLBACK_MOUSE_UP, onMouseUp, this);
75 TextSelection::~TextSelection()
86 ecore_animator_del(m_moveAnimator);
91 ecore_timer_del(m_showTimer);
94 evas_object_event_callback_del(m_viewImpl->view(), EVAS_CALLBACK_MOUSE_UP, onMouseUp);
97 void TextSelection::update()
99 EditorState editorState = m_viewImpl->page()->editorState();
100 if (editorState.updateEditorRectOnly)
103 #if ENABLE(TIZEN_WEBKIT2_GET_TEXT_STYLE_FOR_SELECTION)
104 if (!editorState.shouldIgnoreCompositionSelectionChange)
105 informTextStyleState();
108 if (isTextSelectionMode()) {
109 if (!editorState.selectionIsRange) {
110 if (editorState.isContentEditable && !evas_object_focus_get(m_viewImpl->view())) {
111 WebCore::IntRect caretRect;
112 m_viewImpl->page()->getCaretPosition(caretRect);
113 if (!caretRect.isEmpty())
116 WebCore::IntRect leftRect;
117 WebCore::IntRect rightRect;
118 if (isTextSelectionDowned() || m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect))
121 setIsTextSelectionMode(false);
124 if (!isTextSelectionDowned() && !isTextSelectionHandleDowned()) {
132 void TextSelection::setIsTextSelectionMode(bool isTextSelectionMode)
134 if (!isAutomaticClearEnabled())
137 if (!isTextSelectionMode) {
140 initHandlesMouseDownedStatus();
141 setIsTextSelectionDowned(false);
144 m_isTextSelectionMode = isTextSelectionMode;
147 void TextSelection::clear()
149 EditorState editorState = m_viewImpl->page()->editorState();
150 if (!editorState.selectionIsRange)
153 m_viewImpl->page()->selectionRangeClear();
156 void TextSelection::hide()
163 void TextSelection::updateHandlers()
165 WebCore::IntRect leftRect, rightRect;
166 if (!m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect))
169 m_lastLeftHandleRect = leftRect;
170 m_lastRightHandleRect = rightRect;
172 AffineTransform toEvasTransform = m_viewImpl->transformToScene();
173 WebCore::IntPoint leftEvasPoint = toEvasTransform.mapPoint(leftRect.minXMaxYCorner());
174 WebCore::IntPoint rightEvasPoint = toEvasTransform.mapPoint(rightRect.maxXMaxYCorner());
176 TextSelectionHandle* shownLeftHandle = m_leftHandle;
177 TextSelectionHandle* shownRightHandle = m_rightHandle;
179 if (m_handleMovingDirection == HandleMovingDirectionReverse) {
180 shownLeftHandle = m_rightHandle;
181 shownRightHandle = m_leftHandle;
184 EditorState editorState = m_viewImpl->page()->editorState();
185 if (editorState.isContentEditable) {
186 shownLeftHandle->hide();
187 shownRightHandle->hide();
189 WebCore::IntRect editorRect = editorState.editorRect;
190 WebCore::IntPoint editorLeftEvasPoint = toEvasTransform.mapPoint(editorRect.location());
191 WebCore::IntPoint editorRightEvasPoint = toEvasTransform.mapPoint(editorRect.maxXMaxYCorner());
192 int webViewX, webViewY, webViewWidth, webViewHeight;
194 evas_object_geometry_get(m_viewImpl->view(), &webViewX, &webViewY, &webViewWidth, &webViewHeight);
195 if ((editorLeftEvasPoint.x() <= leftEvasPoint.x() && editorLeftEvasPoint.y() <= leftEvasPoint.y())
196 && (webViewX <= leftEvasPoint.x() && webViewY <= leftEvasPoint.y())) {
197 shownLeftHandle->move(leftEvasPoint, m_viewImpl->transformToScene().mapRect(leftRect));
198 shownLeftHandle->show();
201 if ((editorRightEvasPoint.x() >= rightEvasPoint.x() && editorRightEvasPoint.y() >= rightEvasPoint.y())
202 && ((webViewX + webViewWidth) >= rightEvasPoint.x() && (webViewY <= rightEvasPoint.y() && (webViewY + webViewHeight) >= rightEvasPoint.y()))) {
203 shownRightHandle->move(rightEvasPoint, m_viewImpl->transformToScene().mapRect(rightRect));
204 shownRightHandle->show();
207 shownLeftHandle->move(leftEvasPoint, m_viewImpl->transformToScene().mapRect(leftRect));
208 shownLeftHandle->show();
210 shownRightHandle->move(rightEvasPoint, m_viewImpl->transformToScene().mapRect(rightRect));
211 shownRightHandle->show();
215 void TextSelection::hideHandlers()
217 m_leftHandle->hide();
218 m_rightHandle->hide();
221 void TextSelection::showMagnifier()
226 void TextSelection::hideMagnifier()
231 void TextSelection::updateMagnifier(const IntPoint& position)
233 m_magnifier->update(position);
234 m_magnifier->move(position);
237 void TextSelection::showContextMenu()
242 EditorState editorState = m_viewImpl->page()->editorState();
243 if (!editorState.selectionIsRange && !editorState.isContentEditable)
246 WebCore::IntPoint point;
247 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
248 bool isPresentInViewPort = false;
250 if (editorState.selectionIsRange) {
251 WebCore::IntRect leftRect, rightRect;
252 if (!m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect))
255 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
256 // Checking if this point is in viewport area. If the calcualated
257 // point/Left/Right point are in view port then draw else do not draw the
258 // context menu. Only draw the selection points.
259 FloatRect unscaledRect = FloatRect(m_viewImpl->pageClient->visibleContentRect());
260 unscaledRect.scale(1 / m_viewImpl->pageClient->scaleFactor());
261 IntRect viewportRect = enclosingIntRect(unscaledRect);
263 WebCore::IntPoint visiblePoint = leftRect.center();
264 if (viewportRect.contains(visiblePoint)) {
265 // First check That the modified points are present in view port
266 point = visiblePoint;
267 isPresentInViewPort = true;
268 } else if (viewportRect.contains(leftRect.location())) {
269 // else if the calculated point is not in the view port area the
270 // draw context menu at left point if visible
271 point = leftRect.location();
272 isPresentInViewPort = true;
273 } else if (viewportRect.contains(rightRect.maxXMinYCorner())) {
274 // else if the calculated point is not in the view port area the
275 // draw context menu at right point if visible
276 point = rightRect.maxXMinYCorner();
277 isPresentInViewPort = true;
280 if (isPresentInViewPort && editorState.isContentEditable) {
281 // In case of single line editor box.
282 if (leftRect == rightRect) {
283 // draw context menu at center point of visible selection range in the editor box
284 IntRect editorRect = editorState.editorRect;
285 leftRect.intersect(editorRect);
286 if (!leftRect.isEmpty())
287 point = leftRect.center();
289 // not draw context menu if there is no visible selection range in the editor box
290 isPresentInViewPort = false;
295 point = leftRect.center();
297 } else if (editorState.isContentEditable) {
298 WebCore::IntRect caretRect;
299 m_viewImpl->page()->getCaretPosition(caretRect);
301 if (caretRect.isEmpty())
304 point = caretRect.center();
305 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
306 isPresentInViewPort = true;
309 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
310 if (!isPresentInViewPort)
314 // show context menu if its in viewport else do not show the contextmenu
316 Ewk_View_Smart_Data* smartData = static_cast<Ewk_View_Smart_Data*>(evas_object_smart_data_get(m_viewImpl->view()));
317 if (!smartData || !smartData->api || !smartData->api->mouse_down || !smartData->api->mouse_up)
320 point = m_viewImpl->transformToScene().mapPoint(point);
321 Evas* evas = evas_object_evas_get(m_viewImpl->view());
324 Evas_Event_Mouse_Down mouseDown;
325 mouseDown.button = 3;
326 mouseDown.output.x = mouseDown.canvas.x = point.x();
327 mouseDown.output.y = mouseDown.canvas.y = point.y();
329 mouseDown.modifiers = const_cast<Evas_Modifier*>(evas_key_modifier_get(evas));
330 mouseDown.locks = const_cast<Evas_Lock*>(evas_key_lock_get(evas));
331 mouseDown.flags = EVAS_BUTTON_NONE;
332 mouseDown.timestamp = ecore_time_get() * 1000;
333 mouseDown.event_flags = EVAS_EVENT_FLAG_NONE;
335 smartData->api->mouse_down(smartData, &mouseDown);
338 Evas_Event_Mouse_Up mouseUp;
340 mouseUp.output.x = mouseUp.canvas.x = point.x();
341 mouseUp.output.y = mouseUp.canvas.y = point.y();
343 mouseUp.modifiers = const_cast<Evas_Modifier*>(evas_key_modifier_get(evas));
344 mouseUp.locks = const_cast<Evas_Lock*>(evas_key_lock_get(evas));
345 mouseUp.flags = EVAS_BUTTON_NONE;
346 mouseUp.timestamp = ecore_time_get() * 1000;
347 mouseUp.event_flags = EVAS_EVENT_FLAG_NONE;
349 smartData->api->mouse_up(smartData, &mouseUp);
352 void TextSelection::hideContextMenu()
357 m_viewImpl->page()->hideContextMenu();
360 void TextSelection::setLeftSelectionToEvasPoint(const IntPoint& evasPoint)
362 int result = m_viewImpl->page()->setLeftSelection(m_viewImpl->transformFromScene().mapPoint(evasPoint), m_handleMovingDirection);
364 m_handleMovingDirection = result;
368 void TextSelection::setRightSelectionToEvasPoint(const IntPoint& evasPoint)
370 int result = m_viewImpl->page()->setRightSelection(m_viewImpl->transformFromScene().mapPoint(evasPoint), m_handleMovingDirection);
372 m_handleMovingDirection = result;
377 void TextSelection::handleMouseDown(TextSelectionHandle* handle, const IntPoint& /*position*/)
379 WebCore::IntPoint basePosition;
380 EditorState editorState = m_viewImpl->page()->editorState();
382 if (editorState.selectionIsRange) {
383 WebCore::IntRect leftRect, rightRect;
384 if (!m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect)) {
389 if (handle->isLeft()) {
390 basePosition.setX(leftRect.x());
391 basePosition.setY(leftRect.y() + (leftRect.height()/2));
393 basePosition.setX(rightRect.x() + rightRect.width());
394 basePosition.setY(rightRect.y() + (rightRect.height()/2));
397 handle->setBasePositionForMove(m_viewImpl->transformToScene().mapPoint(basePosition));
402 updateMagnifier(handle->position());
406 void TextSelection::handleMouseMove(TextSelectionHandle* handle, const IntPoint& position)
408 if (handle->isLeft())
409 setLeftSelectionToEvasPoint(position);
411 setRightSelectionToEvasPoint(position);
413 updateMagnifier(handle->position());
416 void TextSelection::handleMouseUp(TextSelectionHandle* /* handle */, const IntPoint& /* position */)
418 m_handleMovingDirection = HandleMovingDirectionNormal;
425 bool TextSelection::isMagnifierVisible()
427 return m_magnifier->isVisible();
430 void TextSelection::updateHandlesAndContextMenu(bool isShow, bool isScrolling)
432 if (isTextSelectionDowned() && !isScrolling) {
437 if (m_viewImpl->gestureClient->isGestureWorking()) {
438 EditorState editorState = m_viewImpl->page()->editorState();
439 if (!editorState.selectionIsRange && editorState.isContentEditable)
440 setIsTextSelectionMode(false);
444 #if ENABLE(TIZEN_WEBKIT2_CONTEXT_MENU_CLIPBOARD)
445 if (m_viewImpl->pageClient->isClipboardWindowOpened())
448 if (m_viewImpl->gestureClient->isGestureWorking())
458 if (isScrolling && isMagnifierVisible())
462 void TextSelection::startMoveAnimator()
464 if (!isEnabled() || !isTextSelectionDowned())
468 m_moveAnimator = ecore_animator_add(moveAnimatorCallback, this);
471 void TextSelection::stopMoveAnimator()
473 if (m_moveAnimator) {
474 ecore_animator_del(m_moveAnimator);
479 void TextSelection::onMouseUp(void* data, Evas*, Evas_Object*, void* eventInfo)
481 static_cast<TextSelection*>(data)->textSelectionUp(IntPoint());
484 Eina_Bool TextSelection::moveAnimatorCallback(void* data)
486 TextSelection* textSelection = static_cast<TextSelection*>(data);
488 Evas_Coord_Point point;
489 evas_pointer_canvas_xy_get(evas_object_evas_get(textSelection->m_viewImpl->view()), &point.x, &point.y);
490 textSelection->textSelectionMove(IntPoint(point.x, point.y));
492 return ECORE_CALLBACK_RENEW;
495 // 'return false' means text selection is not possible for point.
496 // 'return true' means text selection is possible.
497 bool TextSelection::textSelectionDown(const IntPoint& point)
499 // text selection should be ignored when longtap on handle from osp
500 if (!isEnabled() && isTextSelectionHandleDowned())
503 #if ENABLE(TIZEN_ISF_PORT)
504 m_viewImpl->inputMethodContext()->resetIMFContext();
506 setIsTextSelectionMode(false);
508 IntPoint contentsPoint = m_viewImpl->transformFromScene().mapPoint(point);
509 bool result = m_viewImpl->page()->selectClosestWord(contentsPoint);
513 if (isTextSelectionMode()) {
516 setIsTextSelectionMode(true);
518 setIsTextSelectionDowned(true);
520 updateMagnifier(point);
528 static int s_textSelectionMargin = 5;
530 void TextSelection::textSelectionMove(const IntPoint& point)
532 // text selection should be ignored when longtap on handle from osp
533 if (!isEnabled() && isTextSelectionHandleDowned())
536 if (!isTextSelectionMode()) {
541 WebCore::IntPoint viewPoint;
542 EditorState editorState = m_viewImpl->page()->editorState();
543 bool isInEditablePicker = false;
545 #if ENABLE(TIZEN_INPUT_TAG_EXTENSION)
546 if (editorState.isContentEditable) {
547 if (editorState.inputMethodHints == "date"
548 || editorState.inputMethodHints == "datetime"
549 || editorState.inputMethodHints == "datetime-local"
550 || editorState.inputMethodHints == "month"
551 || editorState.inputMethodHints == "time"
552 || editorState.inputMethodHints == "week")
553 isInEditablePicker = true;
557 if (editorState.isContentEditable && !isInEditablePicker) {
558 IntRect mapRect = m_viewImpl->transformToScene().mapRect(editorState.editorRect);
559 IntPoint updatedPoint = point;
560 bool scrolledY = false;
561 if (point.y() < mapRect.y()) {
562 updatedPoint.setY(mapRect.y() + s_textSelectionMargin);
563 if (m_viewImpl->page()->scrollContentByLine(point,WebCore::DirectionBackward)) {
565 updateMagnifier(updatedPoint);
567 } else if (point.y() > mapRect.maxY()) {
568 updatedPoint.setY(mapRect.maxY() - s_textSelectionMargin);
569 if (m_viewImpl->page()->scrollContentByLine(point,WebCore::DirectionForward)) {
571 updateMagnifier(updatedPoint);
575 bool scrolledX = false;
576 if (point.x() < mapRect.x()) {
577 updatedPoint.setX(mapRect.x() + s_textSelectionMargin);
578 if (m_viewImpl->page()->scrollContentByCharacter(point,WebCore::DirectionBackward)) {
580 updateMagnifier(updatedPoint);
582 } else if (point.x() > mapRect.maxX()) {
583 updatedPoint.setX(mapRect.maxX() - s_textSelectionMargin);
584 if (m_viewImpl->page()->scrollContentByCharacter(point,WebCore::DirectionForward)) {
586 updateMagnifier(updatedPoint);
590 if (!scrolledX && !scrolledY) {
591 viewPoint = m_viewImpl->transformFromScene().mapPoint(updatedPoint);
592 m_viewImpl->page()->selectClosestWord(viewPoint);
593 updateMagnifier(updatedPoint);
596 viewPoint = m_viewImpl->transformFromScene().mapPoint(point);
597 m_viewImpl->page()->selectClosestWord(viewPoint);
598 updateMagnifier(point);
603 void TextSelection::textSelectionUp(const IntPoint& point, bool isStartedTextSelectionFromOutside)
605 // text selection should be ignored when longtap on handle from osp
606 if (!isEnabled() && isTextSelectionHandleDowned())
611 if (!isTextSelectionMode() || !isTextSelectionDowned())
614 setIsTextSelectionDowned(false);
617 EditorState editorState = m_viewImpl->page()->editorState();
618 if (editorState.selectionIsRange || editorState.isContentEditable) {
619 if (editorState.selectionIsRange)
623 } else if (!isStartedTextSelectionFromOutside)
624 setIsTextSelectionMode(false);
627 #if ENABLE(TIZEN_WEBKIT2_FOR_MOVING_TEXT_SELECTION_HANDLE_FROM_OSP)
628 TextSelectionHandle* TextSelection::getSelectedHandle(const IntPoint& position)
630 WebCore::IntRect leftHandleRect = m_leftHandle->getHandleRect();
631 if (!leftHandleRect.isEmpty() && leftHandleRect.contains(position))
634 WebCore::IntRect rightHandleRect = m_rightHandle->getHandleRect();
635 if (!rightHandleRect.isEmpty() && rightHandleRect.contains(position))
636 return m_rightHandle;
641 void TextSelection::textSelectionHandleDown(const IntPoint& position)
643 m_selectedHandle = getSelectedHandle(position);
644 if (m_selectedHandle)
645 m_selectedHandle->mouseDown(position);
647 initHandlesMouseDownedStatus();
650 void TextSelection::textSelectionHandleMove(const IntPoint& position)
652 if (m_selectedHandle && isTextSelectionHandleDowned())
653 m_selectedHandle->mouseMove(position);
656 void TextSelection::textSelectionHandleUp()
658 if (m_selectedHandle && isTextSelectionHandleDowned()) {
659 m_selectedHandle->mouseUp();
660 m_selectedHandle = 0;
665 bool TextSelection::isEnabled()
667 return ewk_settings_text_selection_enabled_get(ewk_view_settings_get(m_viewImpl->view()));
670 bool TextSelection::isAutomaticClearEnabled()
672 return ewk_settings_clear_text_selection_automatically_get(ewk_view_settings_get(m_viewImpl->view()));
675 void TextSelection::requestToShow()
678 ecore_timer_del(m_showTimer);
679 m_showTimer = ecore_timer_loop_add((double)200.0/1000.0, showTimerCallback, this);
682 Eina_Bool TextSelection::showTimerCallback(void* data)
684 TextSelection* textSelection = static_cast<TextSelection*>(data);
685 textSelection->showHandlesAndContextMenu();
687 return ECORE_CALLBACK_RENEW;
690 void TextSelection::showHandlesAndContextMenu()
692 WebCore::IntRect leftRect, rightRect;
693 if (m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect)) {
694 if ((leftRect == m_lastLeftHandleRect) && (rightRect == m_lastRightHandleRect)) {
696 ecore_timer_del(m_showTimer);
700 updateHandlesAndContextMenu(true);
703 m_lastLeftHandleRect = leftRect;
704 m_lastRightHandleRect = rightRect;
708 void TextSelection::initHandlesMouseDownedStatus()
710 m_leftHandle->setIsMouseDowned(false);
711 m_rightHandle->setIsMouseDowned(false);
714 void TextSelection::changeContextMenuPosition(WebCore::IntPoint& position)
716 if (m_leftHandle->isTop()) {
717 IntRect handleRect = m_leftHandle->getHandleRect();
718 position.setY(position.y() - handleRect.height());
722 #if ENABLE(TIZEN_WEBKIT2_GET_TEXT_STYLE_FOR_SELECTION)
723 void TextSelection::informTextStyleState()
725 WebCore::IntPoint startPoint, endPoint;
726 WebCore::IntRect leftRect, rightRect;
728 WebCore::IntRect caretRect;
729 m_viewImpl->page()->getCaretPosition(caretRect);
730 if (!caretRect.isEmpty()) {
731 startPoint.setX(caretRect.x());
732 startPoint.setY(caretRect.y() + caretRect.height());
734 endPoint.setX(caretRect.x() + caretRect.width());
735 endPoint.setY(caretRect.y() + caretRect.height());
737 else if (m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect)) {
738 startPoint.setX(leftRect.x());
739 startPoint.setY(leftRect.y() + leftRect.height());
741 endPoint.setX(rightRect.x() + rightRect.width());
742 endPoint.setY(rightRect.y() + rightRect.height());
745 AffineTransform toEvasTransform = m_viewImpl->transformToScene();
746 WebCore::IntPoint startEvasPoint = toEvasTransform.mapPoint(startPoint);
747 WebCore::IntPoint endEvasPoint = toEvasTransform.mapPoint(endPoint);
749 ewkViewTextStyleState(m_viewImpl->view(), startEvasPoint, endEvasPoint);
752 } // namespace WebKit
754 #endif // TIZEN_WEBKIT2_TEXT_SELECTION