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)
53 const Eina_List* defaultThemeList = elm_theme_list_get(0);
57 EINA_LIST_FOREACH(defaultThemeList, l, theme) {
58 char* themePath = elm_theme_list_item_path_get((const char*)theme, 0);
61 m_leftHandle = new TextSelectionHandle(m_viewImpl->view(), themePath, "elm/entry/selection/block_handle_left", true, this);
62 m_rightHandle = new TextSelectionHandle(m_viewImpl->view(), themePath, "elm/entry/selection/block_handle_right", false, this);
69 m_magnifier = new TextSelectionMagnifier(m_viewImpl);
71 evas_object_event_callback_add(m_viewImpl->view(), EVAS_CALLBACK_MOUSE_UP, onMouseUp, this);
74 TextSelection::~TextSelection()
85 ecore_animator_del(m_moveAnimator);
90 ecore_timer_del(m_showTimer);
93 evas_object_event_callback_del(m_viewImpl->view(), EVAS_CALLBACK_MOUSE_UP, onMouseUp);
96 void TextSelection::update()
98 EditorState editorState = m_viewImpl->page()->editorState();
99 if (editorState.updateEditorRectOnly)
102 #if ENABLE(TIZEN_WEBKIT2_GET_TEXT_STYLE_FOR_SELECTION)
103 if (!editorState.shouldIgnoreCompositionSelectionChange)
104 informTextStyleState();
107 if (isTextSelectionMode()) {
108 if (!editorState.selectionIsRange) {
109 if (editorState.isContentEditable && !evas_object_focus_get(m_viewImpl->view())) {
110 WebCore::IntRect caretRect;
111 m_viewImpl->page()->getCaretPosition(caretRect);
112 if (!caretRect.isEmpty())
115 WebCore::IntRect leftRect;
116 WebCore::IntRect rightRect;
117 if (isTextSelectionDowned() || m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect))
120 setIsTextSelectionMode(false);
123 if (!isTextSelectionDowned() && !isTextSelectionHandleDowned()) {
131 void TextSelection::setIsTextSelectionMode(bool isTextSelectionMode)
133 if (!isAutomaticClearEnabled())
136 if (!isTextSelectionMode) {
139 initHandlesMouseDownedStatus();
140 setIsTextSelectionDowned(false);
143 m_isTextSelectionMode = isTextSelectionMode;
146 void TextSelection::clear()
148 EditorState editorState = m_viewImpl->page()->editorState();
149 if (!editorState.selectionIsRange)
152 m_viewImpl->page()->selectionRangeClear();
155 void TextSelection::hide()
162 void TextSelection::updateHandlers()
164 WebCore::IntRect leftRect, rightRect;
165 if (!m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect))
168 m_lastLeftHandleRect = leftRect;
169 m_lastRightHandleRect = rightRect;
171 AffineTransform toEvasTransform = m_viewImpl->transformToScene();
172 WebCore::IntPoint leftEvasPoint = toEvasTransform.mapPoint(leftRect.minXMaxYCorner());
173 WebCore::IntPoint rightEvasPoint = toEvasTransform.mapPoint(rightRect.maxXMaxYCorner());
175 EditorState editorState = m_viewImpl->page()->editorState();
176 if (editorState.isContentEditable) {
177 m_leftHandle->hide();
178 m_rightHandle->hide();
180 WebCore::IntRect editorRect = editorState.editorRect;
181 WebCore::IntPoint editorLeftEvasPoint = toEvasTransform.mapPoint(editorRect.location());
182 WebCore::IntPoint editorRightEvasPoint = toEvasTransform.mapPoint(editorRect.maxXMaxYCorner());
183 int webViewX, webViewY, webViewWidth, webViewHeight;
185 evas_object_geometry_get(m_viewImpl->view(), &webViewX, &webViewY, &webViewWidth, &webViewHeight);
186 if ((editorLeftEvasPoint.x() <= leftEvasPoint.x() && editorLeftEvasPoint.y() <= leftEvasPoint.y())
187 && (webViewX <= leftEvasPoint.x() && webViewY <= leftEvasPoint.y())) {
188 m_leftHandle->move(leftEvasPoint, m_viewImpl->transformToScene().mapRect(leftRect));
189 m_leftHandle->show();
192 if ((editorRightEvasPoint.x() >= rightEvasPoint.x() && editorRightEvasPoint.y() >= rightEvasPoint.y())
193 && ((webViewX + webViewWidth) >= rightEvasPoint.x() && (webViewY <= rightEvasPoint.y() && (webViewY + webViewHeight) >= rightEvasPoint.y()))) {
194 m_rightHandle->move(rightEvasPoint, m_viewImpl->transformToScene().mapRect(rightRect));
195 m_rightHandle->show();
198 m_leftHandle->move(leftEvasPoint, m_viewImpl->transformToScene().mapRect(leftRect));
199 m_leftHandle->show();
201 m_rightHandle->move(rightEvasPoint, m_viewImpl->transformToScene().mapRect(rightRect));
202 m_rightHandle->show();
206 void TextSelection::hideHandlers()
208 m_leftHandle->hide();
209 m_rightHandle->hide();
212 void TextSelection::showMagnifier()
217 void TextSelection::hideMagnifier()
222 void TextSelection::updateMagnifier(const IntPoint& position)
224 m_magnifier->update(position);
225 m_magnifier->move(position);
228 void TextSelection::showContextMenu()
233 EditorState editorState = m_viewImpl->page()->editorState();
234 if (!editorState.selectionIsRange && !editorState.isContentEditable)
237 WebCore::IntPoint point;
238 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
239 bool isPresentInViewPort = false;
241 if (editorState.selectionIsRange) {
242 WebCore::IntRect leftRect, rightRect;
243 if (!m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect))
246 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
247 // Checking if this point is in viewport area. If the calcualated
248 // point/Left/Right point are in view port then draw else do not draw the
249 // context menu. Only draw the selection points.
250 FloatRect unscaledRect = FloatRect(m_viewImpl->pageClient->visibleContentRect());
251 unscaledRect.scale(1 / m_viewImpl->pageClient->scaleFactor());
252 IntRect viewportRect = enclosingIntRect(unscaledRect);
254 WebCore::IntPoint visiblePoint = leftRect.center();
255 if (viewportRect.contains(visiblePoint)) {
256 // First check That the modified points are present in view port
257 point = visiblePoint;
258 isPresentInViewPort = true;
259 } else if (viewportRect.contains(leftRect.location())) {
260 // else if the calculated point is not in the view port area the
261 // draw context menu at left point if visible
262 point = leftRect.location();
263 isPresentInViewPort = true;
264 } else if (viewportRect.contains(rightRect.maxXMinYCorner())) {
265 // else if the calculated point is not in the view port area the
266 // draw context menu at right point if visible
267 point = rightRect.maxXMinYCorner();
268 isPresentInViewPort = true;
271 if (isPresentInViewPort && editorState.isContentEditable) {
272 // In case of single line editor box.
273 if (leftRect == rightRect) {
274 // draw context menu at center point of visible selection range in the editor box
275 IntRect editorRect = editorState.editorRect;
276 leftRect.intersect(editorRect);
277 if (!leftRect.isEmpty())
278 point = leftRect.center();
280 // not draw context menu if there is no visible selection range in the editor box
281 isPresentInViewPort = false;
286 point = leftRect.center();
288 } else if (editorState.isContentEditable) {
289 WebCore::IntRect caretRect;
290 m_viewImpl->page()->getCaretPosition(caretRect);
292 if (caretRect.isEmpty())
295 point = caretRect.center();
296 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
297 isPresentInViewPort = true;
300 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
301 if (!isPresentInViewPort)
305 // show context menu if its in viewport else do not show the contextmenu
307 Ewk_View_Smart_Data* smartData = static_cast<Ewk_View_Smart_Data*>(evas_object_smart_data_get(m_viewImpl->view()));
308 if (!smartData || !smartData->api || !smartData->api->mouse_down || !smartData->api->mouse_up)
311 point = m_viewImpl->transformToScene().mapPoint(point);
312 Evas* evas = evas_object_evas_get(m_viewImpl->view());
315 Evas_Event_Mouse_Down mouseDown;
316 mouseDown.button = 3;
317 mouseDown.output.x = mouseDown.canvas.x = point.x();
318 mouseDown.output.y = mouseDown.canvas.y = point.y();
320 mouseDown.modifiers = const_cast<Evas_Modifier*>(evas_key_modifier_get(evas));
321 mouseDown.locks = const_cast<Evas_Lock*>(evas_key_lock_get(evas));
322 mouseDown.flags = EVAS_BUTTON_NONE;
323 mouseDown.timestamp = ecore_time_get() * 1000;
324 mouseDown.event_flags = EVAS_EVENT_FLAG_NONE;
326 smartData->api->mouse_down(smartData, &mouseDown);
329 Evas_Event_Mouse_Up mouseUp;
331 mouseUp.output.x = mouseUp.canvas.x = point.x();
332 mouseUp.output.y = mouseUp.canvas.y = point.y();
334 mouseUp.modifiers = const_cast<Evas_Modifier*>(evas_key_modifier_get(evas));
335 mouseUp.locks = const_cast<Evas_Lock*>(evas_key_lock_get(evas));
336 mouseUp.flags = EVAS_BUTTON_NONE;
337 mouseUp.timestamp = ecore_time_get() * 1000;
338 mouseUp.event_flags = EVAS_EVENT_FLAG_NONE;
340 smartData->api->mouse_up(smartData, &mouseUp);
343 void TextSelection::hideContextMenu()
348 m_viewImpl->page()->hideContextMenu();
351 void TextSelection::setLeftSelectionToEvasPoint(const IntPoint& evasPoint)
353 m_viewImpl->page()->setLeftSelection(m_viewImpl->transformFromScene().mapPoint(evasPoint));
357 void TextSelection::setRightSelectionToEvasPoint(const IntPoint& evasPoint)
359 m_viewImpl->page()->setRightSelection(m_viewImpl->transformFromScene().mapPoint(evasPoint));
364 void TextSelection::handleMouseDown(TextSelectionHandle* handle, const IntPoint& /*position*/)
366 WebCore::IntPoint basePosition;
367 EditorState editorState = m_viewImpl->page()->editorState();
369 if (editorState.selectionIsRange) {
370 WebCore::IntRect leftRect, rightRect;
371 if (!m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect)) {
376 if (handle->isLeft()) {
377 basePosition.setX(leftRect.x());
378 basePosition.setY(leftRect.y() + (leftRect.height()/2));
380 basePosition.setX(rightRect.x() + rightRect.width());
381 basePosition.setY(rightRect.y() + (rightRect.height()/2));
384 handle->setBasePositionForMove(m_viewImpl->transformToScene().mapPoint(basePosition));
389 updateMagnifier(handle->position());
393 void TextSelection::handleMouseMove(TextSelectionHandle* handle, const IntPoint& position)
395 if (handle->isLeft())
396 setLeftSelectionToEvasPoint(position);
398 setRightSelectionToEvasPoint(position);
400 updateMagnifier(handle->position());
403 void TextSelection::handleMouseUp(TextSelectionHandle* /* handle */, const IntPoint& /* position */)
409 bool TextSelection::isMagnifierVisible()
411 return m_magnifier->isVisible();
414 void TextSelection::updateHandlesAndContextMenu(bool isShow, bool isScrolling)
416 if (isTextSelectionDowned() && !isScrolling) {
421 EditorState editorState = m_viewImpl->page()->editorState();
422 if (!editorState.selectionIsRange && editorState.isContentEditable)
423 setIsTextSelectionMode(false);
426 #if ENABLE(TIZEN_WEBKIT2_CONTEXT_MENU_CLIPBOARD)
427 if (m_viewImpl->pageClient->isClipboardWindowOpened())
430 if (m_viewImpl->gestureClient->isGestureWorking())
440 if (isScrolling && isMagnifierVisible())
444 void TextSelection::startMoveAnimator()
446 if (!isEnabled() || !isTextSelectionDowned())
450 m_moveAnimator = ecore_animator_add(moveAnimatorCallback, this);
453 void TextSelection::stopMoveAnimator()
455 if (m_moveAnimator) {
456 ecore_animator_del(m_moveAnimator);
461 void TextSelection::onMouseUp(void* data, Evas*, Evas_Object*, void* eventInfo)
463 static_cast<TextSelection*>(data)->textSelectionUp(IntPoint());
466 Eina_Bool TextSelection::moveAnimatorCallback(void* data)
468 TextSelection* textSelection = static_cast<TextSelection*>(data);
470 Evas_Coord_Point point;
471 evas_pointer_canvas_xy_get(evas_object_evas_get(textSelection->m_viewImpl->view()), &point.x, &point.y);
472 textSelection->textSelectionMove(IntPoint(point.x, point.y));
474 return ECORE_CALLBACK_RENEW;
477 // 'return false' means text selection is not possible for point.
478 // 'return true' means text selection is possible.
479 bool TextSelection::textSelectionDown(const IntPoint& point)
481 // text selection should be ignored when longtap on handle from osp
482 if (!isEnabled() && isTextSelectionHandleDowned())
485 #if ENABLE(TIZEN_ISF_PORT)
486 m_viewImpl->inputMethodContext()->resetIMFContext();
488 setIsTextSelectionMode(false);
490 IntPoint contentsPoint = m_viewImpl->transformFromScene().mapPoint(point);
491 bool result = m_viewImpl->page()->selectClosestWord(contentsPoint);
495 if (isTextSelectionMode()) {
498 setIsTextSelectionMode(true);
500 setIsTextSelectionDowned(true);
502 updateMagnifier(point);
510 static int s_textSelectionMargin = 5;
512 void TextSelection::textSelectionMove(const IntPoint& point)
514 // text selection should be ignored when longtap on handle from osp
515 if (!isEnabled() && isTextSelectionHandleDowned())
518 if (!isTextSelectionMode()) {
523 WebCore::IntPoint viewPoint;
524 EditorState editorState = m_viewImpl->page()->editorState();
525 bool isInEditablePicker = false;
527 #if ENABLE(TIZEN_INPUT_TAG_EXTENSION)
528 if (editorState.isContentEditable) {
529 if (editorState.inputMethodHints == "date"
530 || editorState.inputMethodHints == "datetime"
531 || editorState.inputMethodHints == "datetime-local"
532 || editorState.inputMethodHints == "month"
533 || editorState.inputMethodHints == "time"
534 || editorState.inputMethodHints == "week")
535 isInEditablePicker = true;
539 if (editorState.isContentEditable && !isInEditablePicker) {
540 IntRect mapRect = m_viewImpl->transformToScene().mapRect(editorState.editorRect);
541 IntPoint updatedPoint = point;
542 bool scrolledY = false;
543 if (point.y() < mapRect.y()) {
544 updatedPoint.setY(mapRect.y() + s_textSelectionMargin);
545 if (m_viewImpl->page()->scrollContentByLine(point,WebCore::DirectionBackward)) {
547 updateMagnifier(updatedPoint);
549 } else if (point.y() > mapRect.maxY()) {
550 updatedPoint.setY(mapRect.maxY() - s_textSelectionMargin);
551 if (m_viewImpl->page()->scrollContentByLine(point,WebCore::DirectionForward)) {
553 updateMagnifier(updatedPoint);
557 bool scrolledX = false;
558 if (point.x() < mapRect.x()) {
559 updatedPoint.setX(mapRect.x() + s_textSelectionMargin);
560 if (m_viewImpl->page()->scrollContentByCharacter(point,WebCore::DirectionBackward)) {
562 updateMagnifier(updatedPoint);
564 } else if (point.x() > mapRect.maxX()) {
565 updatedPoint.setX(mapRect.maxX() - s_textSelectionMargin);
566 if (m_viewImpl->page()->scrollContentByCharacter(point,WebCore::DirectionForward)) {
568 updateMagnifier(updatedPoint);
572 if (!scrolledX && !scrolledY) {
573 viewPoint = m_viewImpl->transformFromScene().mapPoint(updatedPoint);
574 m_viewImpl->page()->selectClosestWord(viewPoint);
575 updateMagnifier(updatedPoint);
578 viewPoint = m_viewImpl->transformFromScene().mapPoint(point);
579 m_viewImpl->page()->selectClosestWord(viewPoint);
580 updateMagnifier(point);
585 void TextSelection::textSelectionUp(const IntPoint& point, bool isStartedTextSelectionFromOutside)
587 // text selection should be ignored when longtap on handle from osp
588 if (!isEnabled() && isTextSelectionHandleDowned())
593 if (!isTextSelectionMode() || !isTextSelectionDowned())
596 setIsTextSelectionDowned(false);
599 EditorState editorState = m_viewImpl->page()->editorState();
600 if (editorState.selectionIsRange || editorState.isContentEditable) {
601 if (editorState.selectionIsRange)
605 } else if (!isStartedTextSelectionFromOutside)
606 setIsTextSelectionMode(false);
609 #if ENABLE(TIZEN_WEBKIT2_FOR_MOVING_TEXT_SELECTION_HANDLE_FROM_OSP)
610 TextSelectionHandle* TextSelection::getSelectedHandle(const IntPoint& position)
612 WebCore::IntRect leftHandleRect = m_leftHandle->getHandleRect();
613 if (!leftHandleRect.isEmpty() && leftHandleRect.contains(position))
616 WebCore::IntRect rightHandleRect = m_rightHandle->getHandleRect();
617 if (!rightHandleRect.isEmpty() && rightHandleRect.contains(position))
618 return m_rightHandle;
623 void TextSelection::textSelectionHandleDown(const IntPoint& position)
625 m_selectedHandle = getSelectedHandle(position);
626 if (m_selectedHandle)
627 m_selectedHandle->mouseDown(position);
629 initHandlesMouseDownedStatus();
632 void TextSelection::textSelectionHandleMove(const IntPoint& position)
634 if (m_selectedHandle && isTextSelectionHandleDowned())
635 m_selectedHandle->mouseMove(position);
638 void TextSelection::textSelectionHandleUp()
640 if (m_selectedHandle && isTextSelectionHandleDowned()) {
641 m_selectedHandle->mouseUp();
642 m_selectedHandle = 0;
647 bool TextSelection::isEnabled()
649 return ewk_settings_text_selection_enabled_get(ewk_view_settings_get(m_viewImpl->view()));
652 bool TextSelection::isAutomaticClearEnabled()
654 return ewk_settings_clear_text_selection_automatically_get(ewk_view_settings_get(m_viewImpl->view()));
657 void TextSelection::requestToShow()
660 ecore_timer_del(m_showTimer);
661 m_showTimer = ecore_timer_loop_add((double)200.0/1000.0, showTimerCallback, this);
664 Eina_Bool TextSelection::showTimerCallback(void* data)
666 TextSelection* textSelection = static_cast<TextSelection*>(data);
667 textSelection->showHandlesAndContextMenu();
669 return ECORE_CALLBACK_RENEW;
672 void TextSelection::showHandlesAndContextMenu()
674 WebCore::IntRect leftRect, rightRect;
675 if (m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect)) {
676 if ((leftRect == m_lastLeftHandleRect) && (rightRect == m_lastRightHandleRect)) {
678 ecore_timer_del(m_showTimer);
682 updateHandlesAndContextMenu(true);
685 m_lastLeftHandleRect = leftRect;
686 m_lastRightHandleRect = rightRect;
690 void TextSelection::initHandlesMouseDownedStatus()
692 m_leftHandle->setIsMouseDowned(false);
693 m_rightHandle->setIsMouseDowned(false);
696 void TextSelection::changeContextMenuPosition(WebCore::IntPoint& position)
698 if (m_leftHandle->isTop()) {
699 IntRect handleRect = m_leftHandle->getHandleRect();
700 position.setY(position.y() - handleRect.height());
704 #if ENABLE(TIZEN_WEBKIT2_GET_TEXT_STYLE_FOR_SELECTION)
705 void TextSelection::informTextStyleState()
707 WebCore::IntPoint startPoint, endPoint;
708 WebCore::IntRect leftRect, rightRect;
710 WebCore::IntRect caretRect;
711 m_viewImpl->page()->getCaretPosition(caretRect);
712 if (!caretRect.isEmpty()) {
713 startPoint.setX(caretRect.x());
714 startPoint.setY(caretRect.y() + caretRect.height());
716 endPoint.setX(caretRect.x() + caretRect.width());
717 endPoint.setY(caretRect.y() + caretRect.height());
719 else if (m_viewImpl->page()->getSelectionHandlers(leftRect, rightRect)) {
720 startPoint.setX(leftRect.x());
721 startPoint.setY(leftRect.y() + leftRect.height());
723 endPoint.setX(rightRect.x() + rightRect.width());
724 endPoint.setY(rightRect.y() + rightRect.height());
727 AffineTransform toEvasTransform = m_viewImpl->transformToScene();
728 WebCore::IntPoint startEvasPoint = toEvasTransform.mapPoint(startPoint);
729 WebCore::IntPoint endEvasPoint = toEvasTransform.mapPoint(endPoint);
731 ewkViewTextStyleState(m_viewImpl->view(), startEvasPoint, endEvasPoint);
734 } // namespace WebKit
736 #endif // TIZEN_WEBKIT2_TEXT_SELECTION