1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the Qt Components project.
9 ** $QT_BEGIN_LICENSE:BSD$
10 ** You may use this file under the terms of the BSD license as follows:
12 ** "Redistribution and use in source and binary forms, with or without
13 ** modification, are permitted provided that the following conditions are
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
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
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."
39 ****************************************************************************/
43 import "RectUtils.js" as Utils
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
57 enabled: !editor.inputMethodComposing
59 LayoutMirroring.enabled: false
60 LayoutMirroring.childrenInherit: true
62 function updateGeometry() {
64 touchTools.handleBegin.updateGeometry();
65 touchTools.handleEnd.updateGeometry();
66 touchTools.contextMenu.calculatePosition(); // Update context menu position
70 function flickEnded() {
71 if (internal.editorHasSelection && internal.selectionVisible())
72 touchTools.contextMenu.show();
74 touchTools.contextMenu.hide();
79 touchToolsLoader.sourceComponent = touchToolsComponent
81 internal.currentTouchPoint = root.mapToItem(editor, mouse.x, mouse.y);
83 if (internal.currentTouchPoint.x < 0)
84 internal.currentTouchPoint.x = 0
86 if (internal.currentTouchPoint.y < 0)
87 internal.currentTouchPoint.y = 0
89 if (internal.tapCounter == 0)
90 internal.touchPoint = internal.currentTouchPoint;
92 editor.forceActiveFocus();
93 touchTools.contextMenu.hide();
94 internal.handleMoved = false;
96 touchTools.handleBegin.viewPortRect = internal.mapViewPortRectToHandle(touchTools.handleBegin);
97 touchTools.handleEnd.viewPortRect = internal.mapViewPortRectToHandle(touchTools.handleEnd);
99 internal.pressedHandle = internal.handleForPoint({x: internal.currentTouchPoint.x, y: internal.currentTouchPoint.y});
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);
111 editor.cursorPosition = editor.selectionEnd
112 editor.select(tempStart, tempEnd);
118 ++internal.tapCounter;
119 if (internal.tapCounter == 1) {
120 internal.onSingleTap();
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();
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;
140 touchTools.magnifier.hide();
141 internal.handleGrabbed();
143 touchTools.contextMenu.hide();
146 if (!editor.readOnly || internal.editorHasSelection)
147 touchTools.magnifier.show();
151 touchTools.magnifier.hide();
153 mouseGrabDisabler.setKeepMouseGrab(root, false);
154 internal.forcedSelection = false;
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;
165 internal.currentTouchPoint = root.mapToItem(editor, mouse.x, mouse.y);
167 if (internal.pressedHandle != null) {
168 internal.hitTestPoint = {x:internal.currentTouchPoint.x, y:internal.currentTouchPoint.y};
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);
181 editor.cursorPosition = newPosition;
183 if (!editor.readOnly || internal.editorHasSelection)
184 touchTools.magnifier.show();
185 internal.handleMoved = true;
192 onTextChanged: internal.onEditorTextChanged
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)
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
215 editor.cursorPosition = editor.positionAt(internal.touchPoint.x, internal.touchPoint.y);
216 touchTools.contextMenu.hide();
217 if (!editor.readOnly)
218 editor.openSoftwareInputPanel()
222 function onDoubleTap() {
223 // assume that the 1st click positions the cursor
225 touchTools.contextMenu.show();
228 function onTripleTap() {
230 touchTools.contextMenu.show();
233 function onEditorTextChanged() {
234 if (touchTools && !internal.editorHasSelection)
235 touchTools.contextMenu.hide();
238 function handleGrabbed() {
239 mouseGrabDisabler.setKeepMouseGrab(root, true);
240 internal.hitTestPoint = {x:internal.currentTouchPoint.x, y:internal.currentTouchPoint.y};
242 internal.forcedSelection = internal.editorHasSelection;
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);
251 // point is in Editor's coordinate system
252 function handleForPoint(point) {
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;
261 if (touchTools.handleEnd.containsPoint(editor.mapToItem(touchTools.handleEnd, point.x, point.y))) {
263 if (pressed != null) {
264 var distance1 = touchTools.handleBegin.pointDistanceFromCenter(point);
265 var distance2 = touchTools.handleEnd.pointDistanceFromCenter(point);
267 if (distance1 < distance2)
271 pressed = touchTools.handleEnd;
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);
283 return Utils.rectIntersectsRect(selectionRect, viewPortRect) ||
284 Utils.rectContainsRect(viewPortRect, selectionRect) ||
285 Utils.rectContainsRect(selectionRect, viewPortRect);
294 id: touchToolsComponent
298 property alias handleBegin: selBegin
299 property alias handleEnd: selEnd
300 property alias contextMenu: cxtMenu
301 property alias magnifier: magnif
303 TextSelectionHandle {
306 objectName: "SelectionBegin"
308 imageSource: privateStyle.imagePath("qtg_fr_textfield_handle_start", root.platformInverted)
309 editorPos: editor.selectionStart
310 visible: editor.selectionStart != editor.selectionEnd
313 TextSelectionHandle { // also acts as the cursor handle when no selection
316 objectName: "SelectionEnd"
318 imageSource: privateStyle.imagePath("qtg_fr_textfield_handle_end", root.platformInverted)
319 editorPos: editor.selectionEnd
321 showImage: internal.hasSelection //show image only in selection mode
328 platformInverted: root.platformInverted
329 copyEnabled: root.copyEnabled
330 cutEnabled: root.cutEnabled
337 contentCenter: internal.hitTestPoint
338 platformInverted: root.platformInverted
346 id: mouseGrabDisabler
352 interval: 400; repeat: false
355 internal.tapCounter = 0;
361 onActiveFocusChanged: {
362 // On focus loss dismiss menu, selection and VKB
363 if (!root.editor.activeFocus) {
365 touchTools.contextMenu.hide()
366 root.editor.select(root.editor.cursorPosition, root.editor.cursorPosition)
367 root.editor.closeSoftwareInputPanel()
374 touchToolsLoader.sourceComponent = touchToolsComponent
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()