--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Components project.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
+** the names of its contributors may be used to endorse or promote
+** products derived from this software without specific prior written
+** permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 1.1
+import "." 1.1
+import "RectUtils.js" as Utils
+
+
+MouseArea {
+ id: root
+
+ property Item editor: parent
+ property alias touchTools: touchToolsLoader.item
+ property real editorScrolledX: 0
+ property real editorScrolledY: 0
+ property bool copyEnabled: false
+ property bool cutEnabled: false
+ property bool platformInverted: false
+
+ enabled: !editor.inputMethodComposing
+
+ LayoutMirroring.enabled: false
+ LayoutMirroring.childrenInherit: true
+
+ function updateGeometry() {
+ if (touchTools) {
+ touchTools.handleBegin.updateGeometry();
+ touchTools.handleEnd.updateGeometry();
+ touchTools.contextMenu.calculatePosition(); // Update context menu position
+ }
+ }
+
+ function flickEnded() {
+ if (internal.editorHasSelection && internal.selectionVisible())
+ touchTools.contextMenu.show();
+ else
+ touchTools.contextMenu.hide();
+ }
+
+ onPressed: {
+ if (!touchTools)
+ touchToolsLoader.sourceComponent = touchToolsComponent
+
+ internal.currentTouchPoint = root.mapToItem(editor, mouse.x, mouse.y);
+
+ if (internal.currentTouchPoint.x < 0)
+ internal.currentTouchPoint.x = 0
+
+ if (internal.currentTouchPoint.y < 0)
+ internal.currentTouchPoint.y = 0
+
+ if (internal.tapCounter == 0)
+ internal.touchPoint = internal.currentTouchPoint;
+
+ editor.forceActiveFocus();
+ touchTools.contextMenu.hide();
+ internal.handleMoved = false;
+
+ touchTools.handleBegin.viewPortRect = internal.mapViewPortRectToHandle(touchTools.handleBegin);
+ touchTools.handleEnd.viewPortRect = internal.mapViewPortRectToHandle(touchTools.handleEnd);
+
+ internal.pressedHandle = internal.handleForPoint({x: internal.currentTouchPoint.x, y: internal.currentTouchPoint.y});
+
+ if (internal.pressedHandle != null) {
+ internal.handleGrabbed();
+ // Position cursor at the pressed selection handle
+ // TODO: Add bug ID!!
+ var tempStart = editor.selectionStart
+ var tempEnd = editor.selectionEnd
+ if (internal.pressedHandle == touchTools.handleBegin) {
+ editor.cursorPosition = editor.selectionStart
+ editor.select(tempEnd, tempStart);
+ } else {
+ editor.cursorPosition = editor.selectionEnd
+ editor.select(tempStart, tempEnd);
+ }
+ }
+ }
+
+ onClicked: {
+ ++internal.tapCounter;
+ if (internal.tapCounter == 1) {
+ internal.onSingleTap();
+ clickTimer.start();
+ } else if (internal.tapCounter == 2 && clickTimer.running) {
+ internal.onDoubleTap();
+ clickTimer.restart();
+ } else if (internal.tapCounter == 3 && clickTimer.running)
+ internal.onTripleTap();
+ }
+
+ onPressAndHold: {
+ clickTimer.stop();
+ internal.tapCounter = 0;
+ internal.longTap = true
+ if (!internal.handleMoved) {
+ if (internal.pressedHandle == null) {
+ // position the cursor under the long tap and make the cursor handle grabbed
+ editor.select(editor.cursorPosition, editor.cursorPosition);
+ editor.cursorPosition = editor.positionAt(internal.touchPoint.x,internal.touchPoint.y);
+ internal.pressedHandle = touchTools.handleEnd;
+ if (editor.readOnly)
+ touchTools.magnifier.hide();
+ internal.handleGrabbed();
+ }
+ touchTools.contextMenu.hide();
+ }
+
+ if (!editor.readOnly || internal.editorHasSelection)
+ touchTools.magnifier.show();
+ }
+
+ onReleased: {
+ touchTools.magnifier.hide();
+
+ mouseGrabDisabler.setKeepMouseGrab(root, false);
+ internal.forcedSelection = false;
+
+ if ((internal.pressedHandle != null && internal.handleMoved) ||
+ (internal.longTap && !editor.readOnly) ||
+ (internal.pressedHandle != null && internal.longTap))
+ touchTools.contextMenu.show();
+ internal.longTap = false;
+ }
+
+ onPositionChanged: {
+
+ internal.currentTouchPoint = root.mapToItem(editor, mouse.x, mouse.y);
+
+ if (internal.pressedHandle != null) {
+ internal.hitTestPoint = {x:internal.currentTouchPoint.x, y:internal.currentTouchPoint.y};
+
+ var newPosition = editor.positionAt(internal.hitTestPoint.x, internal.hitTestPoint.y);
+ if (newPosition >= 0 && newPosition != editor.cursorPosition) {
+ if (internal.hasSelection) {
+ var anchorPos = internal.pressedHandle == touchTools.handleBegin ? editor.selectionEnd
+ : editor.selectionStart
+ if (editor.selectionStart == editor.cursorPosition)
+ anchorPos = editor.selectionEnd;
+ else if (editor.selectionEnd == editor.cursorPosition)
+ anchorPos = editor.selectionStart;
+ editor.select(anchorPos, newPosition);
+ } else {
+ editor.cursorPosition = newPosition;
+ }
+ if (!editor.readOnly || internal.editorHasSelection)
+ touchTools.magnifier.show();
+ internal.handleMoved = true;
+ }
+ }
+ }
+
+ Connections {
+ target: editor
+ onTextChanged: internal.onEditorTextChanged
+ }
+
+ // Private
+ QtObject {
+ id: internal
+
+ property bool forcedSelection: false
+ property bool hasSelection: editorHasSelection || forcedSelection
+ property bool editorHasSelection: editor.selectionStart != editor.selectionEnd
+ property bool handleMoved: false
+ property bool longTap: false
+ property int tapCounter: 0
+ property variant pressedHandle: null
+ property variant hitTestPoint: Qt.point(0, 0)
+ property variant touchPoint: Qt.point(0, 0)
+ property variant currentTouchPoint: Qt.point(0, 0)
+
+ function onSingleTap() {
+ if (!internal.handleMoved) {
+ // need to deselect, because if the cursor position doesn't change the selection remains
+ // even after setting to cursorPosition
+ editor.deselect();
+ editor.cursorPosition = editor.positionAt(internal.touchPoint.x, internal.touchPoint.y);
+ touchTools.contextMenu.hide();
+ if (!editor.readOnly)
+ editor.openSoftwareInputPanel()
+ }
+ }
+
+ function onDoubleTap() {
+ // assume that the 1st click positions the cursor
+ editor.selectWord();
+ touchTools.contextMenu.show();
+ }
+
+ function onTripleTap() {
+ editor.selectAll();
+ touchTools.contextMenu.show();
+ }
+
+ function onEditorTextChanged() {
+ if (touchTools && !internal.editorHasSelection)
+ touchTools.contextMenu.hide();
+ }
+
+ function handleGrabbed() {
+ mouseGrabDisabler.setKeepMouseGrab(root, true);
+ internal.hitTestPoint = {x:internal.currentTouchPoint.x, y:internal.currentTouchPoint.y};
+
+ internal.forcedSelection = internal.editorHasSelection;
+ }
+
+ function mapViewPortRectToHandle(handle) {
+ var position = editor.mapToItem(handle, root.editorScrolledX, root.editorScrolledY);
+ var rect = Qt.rect(position.x, position.y, root.width, root.height);
+ return rect;
+ }
+
+ // point is in Editor's coordinate system
+ function handleForPoint(point) {
+ var pressed = null;
+
+ if (!editor.readOnly || editorHasSelection) { // to avoid moving the cursor handle in read only editor
+ // Find out which handle is being moved
+ if (touchTools.handleBegin.visible &&
+ touchTools.handleBegin.containsPoint(editor.mapToItem(touchTools.handleBegin, point.x, point.y))) {
+ pressed = touchTools.handleBegin;
+ }
+ if (touchTools.handleEnd.containsPoint(editor.mapToItem(touchTools.handleEnd, point.x, point.y))) {
+ var useArea = true;
+ if (pressed != null) {
+ var distance1 = touchTools.handleBegin.pointDistanceFromCenter(point);
+ var distance2 = touchTools.handleEnd.pointDistanceFromCenter(point);
+
+ if (distance1 < distance2)
+ useArea = false;
+ }
+ if (useArea)
+ pressed = touchTools.handleEnd;
+ }
+ }
+ return pressed;
+ }
+
+ function selectionVisible() {
+ var startRect = editor.positionToRectangle(editor.selectionStart);
+ var endRect = editor.positionToRectangle(editor.selectionEnd);
+ var selectionRect = Qt.rect(startRect.x, startRect.y, endRect.x - startRect.x + endRect.width, endRect.y - startRect.y + endRect.height);
+ var viewPortRect = Qt.rect(editorScrolledX, editorScrolledY, editor.width, editor.height);
+
+ return Utils.rectIntersectsRect(selectionRect, viewPortRect) ||
+ Utils.rectContainsRect(viewPortRect, selectionRect) ||
+ Utils.rectContainsRect(selectionRect, viewPortRect);
+ }
+ }
+
+ Loader {
+ id: touchToolsLoader
+ }
+
+ Component {
+ id: touchToolsComponent
+
+ Item {
+ id: touchTools
+ property alias handleBegin: selBegin
+ property alias handleEnd: selEnd
+ property alias contextMenu: cxtMenu
+ property alias magnifier: magnif
+
+ TextSelectionHandle {
+ id: selBegin
+
+ objectName: "SelectionBegin"
+ editor: root.editor
+ imageSource: privateStyle.imagePath("qtg_fr_textfield_handle_start", root.platformInverted)
+ editorPos: editor.selectionStart
+ visible: editor.selectionStart != editor.selectionEnd
+ }
+
+ TextSelectionHandle { // also acts as the cursor handle when no selection
+ id: selEnd
+
+ objectName: "SelectionEnd"
+ editor: root.editor
+ imageSource: privateStyle.imagePath("qtg_fr_textfield_handle_end", root.platformInverted)
+ editorPos: editor.selectionEnd
+ visible: true
+ showImage: internal.hasSelection //show image only in selection mode
+ }
+
+ TextContextMenu {
+ id: cxtMenu
+
+ editor: root.editor
+ platformInverted: root.platformInverted
+ copyEnabled: root.copyEnabled
+ cutEnabled: root.cutEnabled
+ }
+
+ TextMagnifier {
+ id: magnif
+
+ editor: root.editor
+ contentCenter: internal.hitTestPoint
+ platformInverted: root.platformInverted
+
+ }
+ }
+
+ }
+
+ MouseGrabDisabler {
+ id: mouseGrabDisabler
+ }
+
+ Timer {
+ id: clickTimer
+
+ interval: 400; repeat: false
+ onTriggered: {
+ running = false;
+ internal.tapCounter = 0;
+ }
+ }
+
+ Connections {
+ target: root.editor
+ onActiveFocusChanged: {
+ // On focus loss dismiss menu, selection and VKB
+ if (!root.editor.activeFocus) {
+ if (touchTools)
+ touchTools.contextMenu.hide()
+ root.editor.select(root.editor.cursorPosition, root.editor.cursorPosition)
+ root.editor.closeSoftwareInputPanel()
+ }
+ }
+ }
+
+ Keys.onPressed: {
+ if (!touchTools)
+ touchToolsLoader.sourceComponent = touchToolsComponent
+
+ if (internal.editorHasSelection && event.modifiers & Qt.ShiftModifier
+ && (event.key == Qt.Key_Left || event.key == Qt.Key_Right
+ || event.key == Qt.Key_Up || event.key == Qt.Key_Down))
+ touchTools.contextMenu.show()
+ }
+}