Tizen 2.1 base
[framework/web/webkit-efl.git] / Source / WebKit2 / UIProcess / API / efl / tizen / TextSelection.cpp
1 /*
2  * Copyright (C) 2012 Samsung Electronics
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25
26 #include "config.h"
27
28 #if ENABLE(TIZEN_WEBKIT2_TEXT_SELECTION)
29 #include "TextSelection.h"
30
31 #include "EditorState.h"
32 #include "NativeWebMouseEvent.h"
33 #include "ewk_view.h"
34 #include <Elementary.h>
35
36 using namespace WebCore;
37
38 namespace WebKit {
39
40 TextSelection::TextSelection(Evas_Object* viewWidget, WebPageProxy* page, PageClientImpl* pageClient)
41       : m_object(viewWidget),
42         m_page(page),
43         m_pageClient(pageClient),
44         m_isTextSelectionDowned(false),
45         m_isTextSelectionMode(false),
46         m_isTextSelectionEnable(true),
47         m_autoClearTextSelectionMode(true)
48       , m_moveAnimator(0)
49 {
50     ASSERT(viewWidget);
51
52     m_viewImpl = EwkViewImpl::fromEvasObject(m_object);
53
54     const Eina_List* defaultThemeList = elm_theme_list_get(0);
55
56     const Eina_List* l;
57     void* theme;
58     EINA_LIST_FOREACH(defaultThemeList, l, theme) {
59         char* themePath = elm_theme_list_item_path_get((const char*)theme, 0);
60
61         if (themePath) {
62             m_leftHandle = new TextSelectionHandle(m_object, themePath, "elm/entry/selection/block_handle_left", true, this);
63             m_rightHandle = new TextSelectionHandle(m_object, themePath, "elm/entry/selection/block_handle_right", false, this);
64
65             free(themePath);
66             break;
67         }
68     }
69
70     m_magnifier = new TextSelectionMagnifier(m_object, page, pageClient);
71
72     evas_object_event_callback_add(m_object, EVAS_CALLBACK_MOUSE_UP, onMouseUp, this);
73 }
74
75 TextSelection::~TextSelection()
76 {
77     if (m_leftHandle)
78         delete m_leftHandle;
79
80     if (m_rightHandle)
81         delete m_rightHandle;
82
83     delete m_magnifier;
84
85     evas_object_event_callback_del(m_object, EVAS_CALLBACK_MOUSE_UP, onMouseUp);
86 }
87
88 void TextSelection::update()
89 {
90     if (isTextSelectionMode() ) {
91         EditorState editorState = m_page->editorState();
92         if (!editorState.selectionIsRange) {
93             if (editorState.isContentEditable && !evas_object_focus_get(m_object)) {
94                 WebCore::IntRect caretRect;
95                 m_page->getCaretPosition(caretRect);
96                 if (!caretRect.isEmpty())
97                     return;
98             } else {
99                 WebCore::IntRect leftRect;
100                 WebCore::IntRect rightRect;
101                 if (isTextSelectionDowned() || m_page->getSelectionHandlers(leftRect, rightRect))
102                     return;
103
104                 setIsTextSelectionMode(false);
105             }
106 #if ENABLE(TIZEN_WEBKIT2_GET_TEXT_STYLE_FOR_SELECTION)
107             m_page->getTextStyleStateForSelection();
108 #endif
109         } else {
110             if (!isTextSelectionDowned() && !isTextSelectionHandleDowned()) {
111                 updateHandlers();
112                 showContextMenu();
113 #if ENABLE(TIZEN_WEBKIT2_GET_TEXT_STYLE_FOR_SELECTION)
114                 m_page->getTextStyleStateForSelection();
115 #endif
116             }
117         }
118     }
119 #if ENABLE(TIZEN_WEBKIT2_GET_TEXT_STYLE_FOR_SELECTION)
120     else {
121         EditorState editorState = m_page->editorState();
122         if (editorState.isContentEditable && !editorState.selectionIsRange) {
123             WebCore::IntRect caretRect;
124             m_page->getCaretPosition(caretRect);
125             if (!caretRect.isEmpty()) {
126                 m_page->getTextStyleStateForSelection();
127             }
128         }
129     }
130 #endif
131 }
132
133 void TextSelection::setIsTextSelectionMode(bool isTextSelectionMode)
134 {
135     if (!autoClearTextSelectionMode())
136         return;
137
138     if (!isTextSelectionMode) {
139         hide();
140         clear();
141     }
142
143     m_isTextSelectionMode = isTextSelectionMode;
144 }
145
146 void TextSelection::clear()
147 {
148     EditorState editorState = m_page->editorState();
149     if (!editorState.selectionIsRange)
150         return;
151
152     m_page->executeEditCommand("Unselect");
153 }
154
155 void TextSelection::hide()
156 {
157     hideHandlers();
158     hideMagnifier();
159     hideContextMenu();
160 }
161
162 void TextSelection::updateHandlers()
163 {
164     WebCore::IntRect leftRect, rightRect;
165     if (!m_page->getSelectionHandlers(leftRect, rightRect)) {
166         clear();
167         return;
168     }
169
170     AffineTransform toEvasTransform = m_viewImpl->transformToScene();
171     WebCore::IntPoint leftEvasPoint = toEvasTransform.mapPoint(leftRect.minXMaxYCorner());
172     WebCore::IntPoint rightEvasPoint = toEvasTransform.mapPoint(rightRect.maxXMaxYCorner());
173
174     EditorState editorState = m_page->editorState();
175     if (editorState.isContentEditable) {
176         m_leftHandle->hide();
177         m_rightHandle->hide();
178
179         WebCore::IntRect editorRect = editorState.editorRect;
180         WebCore::IntPoint editorLeftEvasPoint = toEvasTransform.mapPoint(editorRect.location());
181         WebCore::IntPoint editorRightEvasPoint = toEvasTransform.mapPoint(editorRect.maxXMaxYCorner());
182         int webViewX, webViewY, webViewWidth, webViewHeight;
183
184         evas_object_geometry_get(m_object, &webViewX, &webViewY, &webViewWidth, &webViewHeight);
185         if ((editorLeftEvasPoint.x() <= leftEvasPoint.x() && editorLeftEvasPoint.y() <= leftEvasPoint.y())
186             && (webViewX <= leftEvasPoint.x() && webViewY <= leftEvasPoint.y())) {
187                 m_leftHandle->move(leftEvasPoint);
188                 m_leftHandle->show();
189         }
190
191         if ((editorRightEvasPoint.x() >= rightEvasPoint.x() && editorRightEvasPoint.y() >= rightEvasPoint.y())
192             && ((webViewX + webViewWidth) >= rightEvasPoint.x() && (webViewY <= rightEvasPoint.y() && (webViewY + webViewHeight) >= rightEvasPoint.y()))) {
193             m_rightHandle->move(rightEvasPoint);
194             m_rightHandle->show();
195         }
196     } else {
197         m_leftHandle->move(leftEvasPoint);
198         m_leftHandle->show();
199
200         m_rightHandle->move(rightEvasPoint);
201         m_rightHandle->show();
202     }
203 }
204
205 void TextSelection::hideHandlers()
206 {
207     m_leftHandle->hide();
208     m_rightHandle->hide();
209 }
210
211 void TextSelection::showMagnifier()
212 {
213     m_magnifier->show();
214 }
215
216 void TextSelection::hideMagnifier()
217 {
218     m_magnifier->hide();
219 }
220
221 void TextSelection::updateMagnifier(const IntPoint& position)
222 {
223     m_magnifier->update(position);
224     m_magnifier->move(position);
225 }
226
227 void TextSelection::showContextMenu()
228 {
229     if (!m_isTextSelectionEnable)
230         return;
231
232     EditorState editorState = m_page->editorState();
233     if (editorState.isInPasswordField)
234         return;
235
236     if (!editorState.selectionIsRange && !editorState.isContentEditable)
237         return;
238
239     WebCore::IntPoint point;
240 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
241     bool isPresentInViewPort = false;
242 #endif
243     if (editorState.selectionIsRange) {
244         WebCore::IntRect leftRect, rightRect;
245         if (!m_page->getSelectionHandlers(leftRect, rightRect)) {
246             clear();
247             return;
248         }
249 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
250         // Checking if this point is in viewport area. If the calcualated
251         // point/Left/Right point are in view port then draw else do not draw the
252         // context menu. Only draw the selection points.
253         FloatRect unscaledRect = FloatRect(m_pageClient->visibleContentRect());
254         unscaledRect.scale(1 / m_pageClient->scaleFactor());
255         IntRect viewportRect = enclosingIntRect(unscaledRect);
256
257         WebCore::IntPoint visiblePoint = leftRect.center();
258         if (viewportRect.contains(visiblePoint)) {
259             // First check That the modified points are present in view port
260             point = visiblePoint;
261             isPresentInViewPort = true;
262         } else if (viewportRect.contains(leftRect.location())) {
263             // else if the calculated point is not in the view port area the
264             // draw context menu at left point if visible
265             point = leftRect.location();
266             isPresentInViewPort = true;
267         } else if (viewportRect.contains(rightRect.maxXMinYCorner())) {
268             // else if the calculated point is not in the view port area the
269             // draw context menu at right point if visible
270             point = rightRect.maxXMinYCorner();
271             isPresentInViewPort = true;
272         }
273 #else
274         point = leftRect.center();
275 #endif
276     } else if (editorState.isContentEditable) {
277         WebCore::IntRect caretRect;
278         m_page->getCaretPosition(caretRect);
279
280         if (caretRect.isEmpty())
281             return;
282
283         point = caretRect.center();
284 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
285         isPresentInViewPort = true;
286 #endif
287     }
288 #if ENABLE(TIZEN_WEBKIT2_TILED_BACKING_STORE)
289     if (!isPresentInViewPort)
290         return;
291 #endif
292
293     // show context menu if its in viewport else do not show the contextmenu
294
295     Ewk_View_Smart_Data* smartData = static_cast<Ewk_View_Smart_Data*>(evas_object_smart_data_get(m_object));
296     if (!smartData || !smartData->api || !smartData->api->mouse_down || !smartData->api->mouse_up)
297         return;
298
299     point = m_viewImpl->transformToScene().mapPoint(point);
300     Evas* evas = evas_object_evas_get(m_object);
301
302     // send mouse down.
303     Evas_Event_Mouse_Down mouseDown;
304     mouseDown.button = 3;
305     mouseDown.output.x = mouseDown.canvas.x = point.x();
306     mouseDown.output.y = mouseDown.canvas.y = point.y();
307     mouseDown.data = 0;
308     mouseDown.modifiers = const_cast<Evas_Modifier*>(evas_key_modifier_get(evas));
309     mouseDown.locks = const_cast<Evas_Lock*>(evas_key_lock_get(evas));
310     mouseDown.flags = EVAS_BUTTON_NONE;
311     mouseDown.timestamp = ecore_time_get() * 1000;
312     mouseDown.event_flags = EVAS_EVENT_FLAG_NONE;
313     mouseDown.dev = 0;
314     smartData->api->mouse_down(smartData, &mouseDown);
315
316     // send mouse up.
317     Evas_Event_Mouse_Up mouseUp;
318     mouseUp.button = 3;
319     mouseUp.output.x = mouseUp.canvas.x = point.x();
320     mouseUp.output.y = mouseUp.canvas.y = point.y();
321     mouseUp.data = 0;
322     mouseUp.modifiers = const_cast<Evas_Modifier*>(evas_key_modifier_get(evas));
323     mouseUp.locks = const_cast<Evas_Lock*>(evas_key_lock_get(evas));
324     mouseUp.flags = EVAS_BUTTON_NONE;
325     mouseUp.timestamp = ecore_time_get() * 1000;
326     mouseUp.event_flags = EVAS_EVENT_FLAG_NONE;
327     mouseUp.dev = 0;
328     smartData->api->mouse_up(smartData, &mouseUp);
329 }
330
331 void TextSelection::hideContextMenu()
332 {
333     if (!m_isTextSelectionEnable)
334         return;
335
336     m_page->hideContextMenu();
337 }
338
339 void TextSelection::setLeftSelectionToEvasPoint(const IntPoint& evasPoint)
340 {
341     m_page->setLeftSelection(m_viewImpl->transformFromScene().mapPoint(evasPoint));
342     updateHandlers();
343 }
344
345 void TextSelection::setRightSelectionToEvasPoint(const IntPoint& evasPoint)
346 {
347     m_page->setRightSelection(m_viewImpl->transformFromScene().mapPoint(evasPoint));
348     updateHandlers();
349 }
350
351 // handle callbacks
352 void TextSelection::handleMouseDown(TextSelectionHandle* handle, const IntPoint& /*position*/)
353 {
354     WebCore::IntPoint basePosition;
355     EditorState editorState = m_page->editorState();
356
357     if (editorState.selectionIsRange) {
358         WebCore::IntRect leftRect, rightRect;
359         if (!m_page->getSelectionHandlers(leftRect, rightRect)) {
360             clear();
361             return;
362         }
363
364         if (handle->isLeft()) {
365             basePosition.setX(leftRect.x());
366             basePosition.setY(leftRect.y() + (leftRect.height()/2));
367         } else {
368             basePosition.setX(rightRect.x() + rightRect.width());
369             basePosition.setY(rightRect.y() + (rightRect.height()/2));
370         }
371
372         handle->setBasePositionForMove(m_viewImpl->transformToScene().mapPoint(basePosition));
373     } else
374         return;
375
376     hideContextMenu();
377     updateMagnifier(handle->position());
378     showMagnifier();
379 }
380
381 void TextSelection::handleMouseMove(TextSelectionHandle* handle, const IntPoint& position)
382 {
383     if (handle->isLeft())
384         setLeftSelectionToEvasPoint(position);
385     else
386         setRightSelectionToEvasPoint(position);
387
388     updateMagnifier(handle->position());
389 }
390
391 void TextSelection::handleMouseUp(TextSelectionHandle* /* handle */, const IntPoint& /* position */)
392 {
393     hideMagnifier();
394     showContextMenu();
395 #if ENABLE(TIZEN_WEBKIT2_GET_TEXT_STYLE_FOR_SELECTION)
396     m_page->getTextStyleStateForSelection();
397 #endif
398 }
399
400 bool TextSelection::isMagnifierVisible()
401 {
402     return m_magnifier->isVisible();
403 }
404
405 void TextSelection::updateHandlesAndContextMenu(bool isShow, bool isScrolling)
406 {
407     if (isTextSelectionDowned() && !isScrolling) {
408         showMagnifier();
409         return;
410     }
411
412     if (isShow) {
413         updateHandlers();
414         showContextMenu();
415     } else {
416         hideHandlers();
417         hideContextMenu();
418     }
419
420     if (isScrolling && isMagnifierVisible())
421         hideMagnifier();
422 }
423
424 void TextSelection::startMoveAnimator()
425 {
426     if (!isTextSelectionEnable() || !isTextSelectionDowned())
427         return;
428
429     if (!m_moveAnimator)
430         m_moveAnimator = ecore_animator_add(moveAnimatorCallback, this);
431 }
432
433 void TextSelection::stopMoveAnimator()
434 {
435     if (m_moveAnimator) {
436         ecore_animator_del(m_moveAnimator);
437         m_moveAnimator = 0;
438     }
439 }
440
441 void TextSelection::onMouseUp(void* data, Evas*, Evas_Object*, void* eventInfo)
442 {
443     static_cast<TextSelection*>(data)->textSelectionUp(IntPoint());
444 }
445
446 Eina_Bool TextSelection::moveAnimatorCallback(void* data)
447 {
448     TextSelection* textSelection = static_cast<TextSelection*>(data);
449
450     Evas_Coord_Point point;
451     evas_pointer_canvas_xy_get(evas_object_evas_get(textSelection->m_object), &point.x, &point.y);
452     textSelection->textSelectionMove(IntPoint(point.x, point.y));
453
454     return ECORE_CALLBACK_RENEW;
455 }
456
457 // 'return false' means text selection is not possible for point.
458 // 'return true' means text selection is possible, but it can be succeed or failed for some reason.
459 bool TextSelection::textSelectionDown(const IntPoint& point, bool isStartedTextSelectionFromOutside)
460 {
461     setIsTextSelectionMode(false);
462
463     IntPoint viewPoint = m_viewImpl->transformFromScene().mapPoint(point);
464     WebHitTestResult::Data hitTestResultData;
465 #if ENABLE(TIZEN_WEBKIT2_HIT_TEST)
466     hitTestResultData = m_pageClient->page()->hitTestResultAtPoint(viewPoint);
467 #endif
468
469     // If node under point has link, we can not process text selection.
470     if (!hitTestResultData.absoluteImageURL.isEmpty()
471 #if ENABLE(TIZEN_DRAG_SUPPORT)
472         || hitTestResultData.isDragSupport
473 #endif
474         || !hitTestResultData.absoluteLinkURL.isEmpty()
475         || !hitTestResultData.absoluteMediaURL.isEmpty())
476         return false;
477
478     // Process gesture_end(EWK_GESTURE_TAP) to activate the editing if node under point is editable.
479     if (hitTestResultData.context & WebHitTestResult::HitTestResultContextEditable) {
480         const Ewk_View_Smart_Data* smartData = static_cast<Ewk_View_Smart_Data*>(evas_object_smart_data_get(m_pageClient->viewWidget()));
481         if (smartData->api && smartData->api->gesture_end) {
482             Ewk_Event_Gesture gestureEvent = {EWK_GESTURE_TAP, {point.x(), point.y()}, {0, 0}, 0, 1, ecore_time_get() * 1000};
483             smartData->api->gesture_end(const_cast<Ewk_View_Smart_Data*>(smartData), &gestureEvent);
484         }
485     }
486
487     bool result = m_page->selectClosestWord(viewPoint, isStartedTextSelectionFromOutside);
488     if (!result)
489         return true;
490
491     if (isTextSelectionMode()) {
492         hide();
493     } else {
494         setIsTextSelectionMode(true);
495     }
496     setIsTextSelectionDowned(true);
497
498     updateMagnifier(point);
499     showMagnifier();
500
501     startMoveAnimator();
502
503     return true;
504 }
505
506 void TextSelection::textSelectionMove(const IntPoint& point, bool isStartedTextSelectionFromOutside)
507 {
508     if (!isTextSelectionMode()) {
509         stopMoveAnimator();
510         return;
511     }
512
513     WebCore::IntPoint viewPoint = m_viewImpl->transformFromScene().mapPoint(point);
514     m_page->selectClosestWord(viewPoint, isStartedTextSelectionFromOutside);
515
516     updateMagnifier(point);
517     showMagnifier();
518 }
519
520 void TextSelection::textSelectionUp(const IntPoint& point, bool isStartedTextSelectionFromOutside)
521 {
522     stopMoveAnimator();
523
524     if (!isTextSelectionMode())
525         return;
526
527     setIsTextSelectionDowned(false);
528     hideMagnifier();
529
530     EditorState editorState = m_page->editorState();
531     if (editorState.selectionIsRange || editorState.isContentEditable) {
532         if (editorState.selectionIsRange)
533             updateHandlers();
534
535         showContextMenu();
536     } else if (!isStartedTextSelectionFromOutside)
537         setIsTextSelectionMode(false);
538
539 #if ENABLE(TIZEN_WEBKIT2_GET_TEXT_STYLE_FOR_SELECTION)
540     m_page->getTextStyleStateForSelection();
541 #endif
542 }
543
544 #if ENABLE(TIZEN_WEBKIT2_FOR_MOVING_TEXT_SELECTION_HANDLE_FROM_OSP)
545 TextSelectionHandle* TextSelection::getSelectedHandle(const IntPoint& position)
546 {
547     WebCore::IntRect leftHandleRect = m_leftHandle->getHandleRect();
548     if (leftHandleRect.contains(position))
549         return m_leftHandle;
550
551     WebCore::IntRect rightHandleRect = m_rightHandle->getHandleRect();
552     if (rightHandleRect.contains(position))
553         return m_rightHandle;
554
555     return 0;
556 }
557
558 void TextSelection::textSelectionHandleDown(const IntPoint& position)
559 {
560     TextSelectionHandle* selectedHandle = getSelectedHandle(position);
561     if (selectedHandle) {
562         selectedHandle->mouseDown(position);
563         if (selectedHandle->isMouseDowned())
564             return;
565     }
566
567     EditorState editorState = m_page->editorState();
568     if (editorState.isContentEditable)
569         setIsTextSelectionMode(false);
570 }
571
572 void TextSelection::textSelectionHandleMove(const IntPoint& position)
573 {
574     if (isTextSelectionHandleDowned()) {
575         TextSelectionHandle* selectedHandle = m_leftHandle->isMouseDowned() ? m_leftHandle : m_rightHandle;
576         selectedHandle->mouseMove(position);
577     }
578 }
579
580 void TextSelection::textSelectionHandleUp()
581 {
582     if (isTextSelectionHandleDowned()) {
583         TextSelectionHandle* selectedHandle = m_leftHandle->isMouseDowned() ? m_leftHandle : m_rightHandle;
584         selectedHandle->mouseUp();
585     }
586 }
587 #endif
588
589 } // namespace WebKit
590
591 #endif // TIZEN_WEBKIT2_TEXT_SELECTION