added android components
[mardrone] / mardrone / imports / com / nokia / android.1.1 / TextTouchController.qml
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Qt Components project.
8 **
9 ** $QT_BEGIN_LICENSE:BSD$
10 ** You may use this file under the terms of the BSD license as follows:
11 **
12 ** "Redistribution and use in source and binary forms, with or without
13 ** modification, are permitted provided that the following conditions are
14 ** met:
15 **   * Redistributions of source code must retain the above copyright
16 **     notice, this list of conditions and the following disclaimer.
17 **   * Redistributions in binary form must reproduce the above copyright
18 **     notice, this list of conditions and the following disclaimer in
19 **     the documentation and/or other materials provided with the
20 **     distribution.
21 **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
22 **     the names of its contributors may be used to endorse or promote
23 **     products derived from this software without specific prior written
24 **     permission.
25 **
26 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 import QtQuick 1.1
42 import "." 1.1
43 import "RectUtils.js" as Utils
44
45
46 MouseArea {
47     id: root
48
49     property Item editor: parent
50     property alias touchTools: touchToolsLoader.item
51     property real editorScrolledX: 0
52     property real editorScrolledY: 0
53     property bool copyEnabled: false
54     property bool cutEnabled: false
55     property bool platformInverted: false
56
57     enabled: !editor.inputMethodComposing
58
59     LayoutMirroring.enabled: false
60     LayoutMirroring.childrenInherit: true
61
62     function updateGeometry() {
63         if (touchTools) {
64             touchTools.handleBegin.updateGeometry();
65             touchTools.handleEnd.updateGeometry();
66             touchTools.contextMenu.calculatePosition(); // Update context menu position
67         }
68     }
69
70     function flickEnded() {
71         if (internal.editorHasSelection && internal.selectionVisible())
72             touchTools.contextMenu.show();
73         else
74             touchTools.contextMenu.hide();
75     }
76
77     onPressed: {
78         if (!touchTools)
79             touchToolsLoader.sourceComponent = touchToolsComponent
80
81         internal.currentTouchPoint = root.mapToItem(editor, mouse.x, mouse.y);
82
83         if (internal.currentTouchPoint.x < 0)
84             internal.currentTouchPoint.x = 0
85
86         if (internal.currentTouchPoint.y < 0)
87             internal.currentTouchPoint.y = 0
88
89         if (internal.tapCounter == 0)
90             internal.touchPoint = internal.currentTouchPoint;
91
92         editor.forceActiveFocus();
93         touchTools.contextMenu.hide();
94         internal.handleMoved = false;
95
96         touchTools.handleBegin.viewPortRect = internal.mapViewPortRectToHandle(touchTools.handleBegin);
97         touchTools.handleEnd.viewPortRect = internal.mapViewPortRectToHandle(touchTools.handleEnd);
98
99         internal.pressedHandle = internal.handleForPoint({x: internal.currentTouchPoint.x, y: internal.currentTouchPoint.y});
100
101         if (internal.pressedHandle != null) {
102             internal.handleGrabbed();
103             // Position cursor at the pressed selection handle
104             // TODO: Add bug ID!!
105             var tempStart = editor.selectionStart
106             var tempEnd = editor.selectionEnd
107             if (internal.pressedHandle == touchTools.handleBegin) {
108                 editor.cursorPosition = editor.selectionStart
109                 editor.select(tempEnd, tempStart);
110             } else {
111                 editor.cursorPosition = editor.selectionEnd
112                 editor.select(tempStart, tempEnd);
113             }
114         }
115     }
116
117     onClicked: {
118         ++internal.tapCounter;
119         if (internal.tapCounter == 1) {
120             internal.onSingleTap();
121             clickTimer.start();
122         } else if (internal.tapCounter == 2 && clickTimer.running) {
123             internal.onDoubleTap();
124             clickTimer.restart();
125         } else if (internal.tapCounter == 3 && clickTimer.running)
126             internal.onTripleTap();
127     }
128
129     onPressAndHold: {
130         clickTimer.stop();
131         internal.tapCounter = 0;
132         internal.longTap = true
133         if (!internal.handleMoved) {
134             if (internal.pressedHandle == null) {
135                 // position the cursor under the long tap and make the cursor handle grabbed
136                 editor.select(editor.cursorPosition, editor.cursorPosition);
137                 editor.cursorPosition = editor.positionAt(internal.touchPoint.x,internal.touchPoint.y);
138                 internal.pressedHandle = touchTools.handleEnd;
139                 if (editor.readOnly)
140                     touchTools.magnifier.hide();
141                 internal.handleGrabbed();
142             }
143             touchTools.contextMenu.hide();
144         }
145
146         if (!editor.readOnly || internal.editorHasSelection)
147             touchTools.magnifier.show();
148     }
149
150     onReleased: {
151         touchTools.magnifier.hide();
152
153         mouseGrabDisabler.setKeepMouseGrab(root, false);
154         internal.forcedSelection = false;
155
156         if ((internal.pressedHandle != null && internal.handleMoved) ||
157            (internal.longTap && !editor.readOnly) ||
158            (internal.pressedHandle != null && internal.longTap))
159             touchTools.contextMenu.show();
160         internal.longTap = false;
161     }
162
163     onPositionChanged: {
164
165         internal.currentTouchPoint = root.mapToItem(editor, mouse.x, mouse.y);
166
167         if (internal.pressedHandle != null) {
168             internal.hitTestPoint = {x:internal.currentTouchPoint.x, y:internal.currentTouchPoint.y};
169
170             var newPosition = editor.positionAt(internal.hitTestPoint.x, internal.hitTestPoint.y);
171             if (newPosition >= 0 && newPosition != editor.cursorPosition) {
172                 if (internal.hasSelection) {
173                     var anchorPos = internal.pressedHandle == touchTools.handleBegin ? editor.selectionEnd
174                                                                                      : editor.selectionStart
175                     if (editor.selectionStart == editor.cursorPosition)
176                         anchorPos = editor.selectionEnd;
177                     else if (editor.selectionEnd == editor.cursorPosition)
178                         anchorPos = editor.selectionStart;
179                     editor.select(anchorPos, newPosition);
180                 } else {
181                     editor.cursorPosition = newPosition;
182                 }
183                 if (!editor.readOnly || internal.editorHasSelection)
184                     touchTools.magnifier.show();
185                 internal.handleMoved = true;
186             }
187         }
188     }
189
190     Connections {
191         target: editor
192         onTextChanged: internal.onEditorTextChanged
193     }
194
195     // Private
196     QtObject {
197         id: internal
198
199         property bool forcedSelection: false
200         property bool hasSelection: editorHasSelection || forcedSelection
201         property bool editorHasSelection: editor.selectionStart != editor.selectionEnd
202         property bool handleMoved: false
203         property bool longTap: false
204         property int tapCounter: 0
205         property variant pressedHandle: null
206         property variant hitTestPoint: Qt.point(0, 0)
207         property variant touchPoint: Qt.point(0, 0)
208         property variant currentTouchPoint: Qt.point(0, 0)
209
210         function onSingleTap() {
211             if (!internal.handleMoved) {
212                 // need to deselect, because if the cursor position doesn't change the selection remains
213                 // even after setting to cursorPosition
214                 editor.deselect();
215                 editor.cursorPosition = editor.positionAt(internal.touchPoint.x, internal.touchPoint.y);
216                 touchTools.contextMenu.hide();
217                 if (!editor.readOnly)
218                     editor.openSoftwareInputPanel()
219             }
220         }
221
222         function onDoubleTap() {
223             // assume that the 1st click positions the cursor
224             editor.selectWord();
225             touchTools.contextMenu.show();
226         }
227
228         function onTripleTap() {
229             editor.selectAll();
230             touchTools.contextMenu.show();
231         }
232
233         function onEditorTextChanged() {
234             if (touchTools && !internal.editorHasSelection)
235                 touchTools.contextMenu.hide();
236         }
237
238         function handleGrabbed() {
239             mouseGrabDisabler.setKeepMouseGrab(root, true);
240             internal.hitTestPoint = {x:internal.currentTouchPoint.x, y:internal.currentTouchPoint.y};
241
242             internal.forcedSelection = internal.editorHasSelection;
243         }
244
245         function mapViewPortRectToHandle(handle) {
246             var position = editor.mapToItem(handle, root.editorScrolledX, root.editorScrolledY);
247             var rect = Qt.rect(position.x, position.y, root.width, root.height);
248             return rect;
249         }
250
251         // point is in Editor's coordinate system
252         function handleForPoint(point) {
253             var pressed = null;
254
255             if (!editor.readOnly || editorHasSelection) { // to avoid moving the cursor handle in read only editor
256                 // Find out which handle is being moved
257                 if (touchTools.handleBegin.visible &&
258                     touchTools.handleBegin.containsPoint(editor.mapToItem(touchTools.handleBegin, point.x, point.y))) {
259                     pressed = touchTools.handleBegin;
260                 }
261                 if (touchTools.handleEnd.containsPoint(editor.mapToItem(touchTools.handleEnd, point.x, point.y))) {
262                     var useArea = true;
263                     if (pressed != null) {
264                         var distance1 = touchTools.handleBegin.pointDistanceFromCenter(point);
265                         var distance2 = touchTools.handleEnd.pointDistanceFromCenter(point);
266
267                         if (distance1 < distance2)
268                             useArea = false;
269                     }
270                     if (useArea)
271                         pressed = touchTools.handleEnd;
272                 }
273             }
274             return pressed;
275         }
276
277         function selectionVisible() {
278             var startRect = editor.positionToRectangle(editor.selectionStart);
279             var endRect = editor.positionToRectangle(editor.selectionEnd);
280             var selectionRect = Qt.rect(startRect.x, startRect.y, endRect.x - startRect.x + endRect.width, endRect.y - startRect.y + endRect.height);
281             var viewPortRect = Qt.rect(editorScrolledX, editorScrolledY, editor.width, editor.height);
282
283             return Utils.rectIntersectsRect(selectionRect, viewPortRect) ||
284                    Utils.rectContainsRect(viewPortRect, selectionRect) ||
285                    Utils.rectContainsRect(selectionRect, viewPortRect);
286         }
287     }
288
289     Loader {
290         id: touchToolsLoader
291     }
292
293     Component {
294         id: touchToolsComponent
295
296         Item {
297             id: touchTools
298             property alias handleBegin: selBegin
299             property alias handleEnd: selEnd
300             property alias contextMenu: cxtMenu
301             property alias magnifier: magnif
302
303             TextSelectionHandle {
304                 id: selBegin
305
306                 objectName: "SelectionBegin"
307                 editor: root.editor
308                 imageSource: privateStyle.imagePath("qtg_fr_textfield_handle_start", root.platformInverted)
309                 editorPos: editor.selectionStart
310                 visible: editor.selectionStart != editor.selectionEnd
311             }
312
313             TextSelectionHandle { // also acts as the cursor handle when no selection
314                 id: selEnd
315
316                 objectName: "SelectionEnd"
317                 editor: root.editor
318                 imageSource: privateStyle.imagePath("qtg_fr_textfield_handle_end", root.platformInverted)
319                 editorPos: editor.selectionEnd
320                 visible: true
321                 showImage: internal.hasSelection //show image only in selection mode
322             }
323
324             TextContextMenu {
325                 id: cxtMenu
326
327                 editor: root.editor
328                 platformInverted: root.platformInverted
329                 copyEnabled: root.copyEnabled
330                 cutEnabled: root.cutEnabled
331             }
332
333             TextMagnifier {
334                 id: magnif
335
336                 editor: root.editor
337                 contentCenter: internal.hitTestPoint
338                 platformInverted: root.platformInverted
339
340             }
341         }
342
343     }
344
345     MouseGrabDisabler {
346         id: mouseGrabDisabler
347     }
348
349     Timer {
350         id: clickTimer
351
352         interval: 400; repeat: false
353         onTriggered: {
354             running = false;
355             internal.tapCounter = 0;
356         }
357     }
358
359     Connections {
360         target: root.editor
361         onActiveFocusChanged: {
362             // On focus loss dismiss menu, selection and VKB
363             if (!root.editor.activeFocus) {
364                 if (touchTools)
365                     touchTools.contextMenu.hide()
366                 root.editor.select(root.editor.cursorPosition, root.editor.cursorPosition)
367                 root.editor.closeSoftwareInputPanel()
368             }
369         }
370     }
371
372     Keys.onPressed: {
373         if (!touchTools)
374             touchToolsLoader.sourceComponent = touchToolsComponent
375
376         if (internal.editorHasSelection && event.modifiers & Qt.ShiftModifier
377             && (event.key == Qt.Key_Left || event.key == Qt.Key_Right
378             || event.key == Qt.Key_Up || event.key == Qt.Key_Down))
379             touchTools.contextMenu.show()
380     }
381 }