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 Qt.labs.components 1.1
44 import "UIConstants.js" as UI
45 import "EditBubble.js" as Popup
46 import "TextAreaHelper.js" as TextAreaHelper
47 import "Magnifier.js" as MagnifierPopup
53 property alias text: textEdit.text
54 property alias placeholderText: prompt.text
56 property alias font: textEdit.font
57 property alias cursorPosition: textEdit.cursorPosition
58 property alias readOnly: textEdit.readOnly
60 property alias horizontalAlignment: textEdit.horizontalAlignment
61 property alias verticalAlignment: textEdit.verticalAlignment
63 property alias selectedText: textEdit.selectedText
64 property alias selectionStart: textEdit.selectionStart
65 property alias selectionEnd: textEdit.selectionEnd
67 property alias wrapMode: textEdit.wrapMode
68 property alias textFormat: textEdit.textFormat
69 // Property enableSoftwareInputPanel is DEPRECATED
70 property alias enableSoftwareInputPanel: textEdit.activeFocusOnPress
72 property alias inputMethodHints: textEdit.inputMethodHints
74 property bool errorHighlight: false
76 property Item platformSipAttributes
78 property bool platformEnableEditBubble: true
80 property Item platformStyle: TextAreaStyle {}
81 property alias style: root.platformStyle
83 property alias platformPreedit: inputMethodObserver.preedit
85 onPlatformSipAttributesChanged: {
86 platformSipAttributes.registerInputElement(textEdit)
101 // ensure propagation of forceActiveFocus
102 function forceActiveFocus() {
103 textEdit.forceActiveFocus()
106 function select(start, end) {
107 textEdit.select(start, end)
110 function selectAll() {
114 function selectWord() {
115 textEdit.selectWord()
118 function positionAt(x, y) {
119 var p = mapToItem(textEdit, x, y);
120 return textEdit.positionAt(p.x, p.y)
123 function positionToRectangle(pos) {
124 var rect = textEdit.positionToRectangle(pos)
125 var point = mapFromItem(textEdit, rect.x, rect.y)
126 rect.x = point.x; rect.y = point.y
130 function closeSoftwareInputPanel() {
131 console.log("TextArea's function closeSoftwareInputPanel is deprecated. Use function platformCloseSoftwareInputPanel instead.")
132 platformCloseSoftwareInputPanel()
135 function platformCloseSoftwareInputPanel() {
136 inputContext.simulateSipClose();
137 textEdit.closeSoftwareInputPanel();
140 function openSoftwareInputPanel() {
141 console.log("TextArea's function openSoftwareInputPanel is deprecated. Use function platformOpenSoftwareInputPanel instead.")
142 platformOpenSoftwareInputPanel()
145 function platformOpenSoftwareInputPanel() {
146 inputContext.simulateSipOpen();
147 textEdit.openSoftwareInputPanel();
151 target: platformWindow
154 if(platformWindow.active) {
157 platformOpenSoftwareInputPanel();
158 repositionTimer.running = true;
163 platformCloseSoftwareInputPanel();
164 Popup.close(textEdit);
169 onAnimatingChanged: {
170 if (!platformWindow.animating && root.activeFocus) {
171 TextAreaHelper.repositionFlickable(contentMovingAnimation);
177 property int __preeditDisabledMask: Qt.ImhHiddenText|
178 Qt.ImhNoPredictiveText|
180 Qt.ImhFormattedNumbersOnly|
181 Qt.ImhDialableCharactersOnly|
182 Qt.ImhEmailCharactersOnly|
183 Qt.ImhUrlCharactersOnly
185 implicitWidth: platformStyle.defaultWidth
186 implicitHeight: Math.max (UI.FIELD_DEFAULT_HEIGHT,
187 textEdit.height + (UI.FIELD_DEFAULT_HEIGHT - font.pixelSize))
189 onActiveFocusChanged: {
192 platformOpenSoftwareInputPanel();
193 repositionTimer.running = true;
194 } else if (!activeFocus) {
196 platformCloseSoftwareInputPanel();
198 Popup.close(textEdit);
204 source: errorHighlight?
205 platformStyle.backgroundError:
207 platformStyle.backgroundDisabled:
208 textEdit.activeFocus?
209 platformStyle.backgroundSelected:
210 platformStyle.background
213 border.left: root.platformStyle.backgroundCornerMargin; border.top: root.platformStyle.backgroundCornerMargin
214 border.right: root.platformStyle.backgroundCornerMargin; border.bottom: root.platformStyle.backgroundCornerMargin
221 anchors.leftMargin: UI.PADDING_XLARGE
222 anchors.rightMargin: UI.PADDING_XLARGE
223 anchors.topMargin: (UI.FIELD_DEFAULT_HEIGHT - font.pixelSize) / 2
224 anchors.bottomMargin: (UI.FIELD_DEFAULT_HEIGHT - font.pixelSize) / 2
226 font: root.platformStyle.textFont
227 color: root.platformStyle.promptTextColor
228 elide: Text.ElideRight
230 // opacity for default state
236 // memory allocation optimization: cursorPosition is checked to minimize displayText evaluations
237 when: !root.activeFocus && textEdit.cursorPosition == 0 && !textEdit.text && prompt.text && !textEdit.inputMethodComposing
238 PropertyChanges { target: prompt; opacity: 1.0; }
242 // memory allocation optimization: cursorPosition is checked to minimize displayText evaluations
243 when: root.activeFocus && textEdit.cursorPosition == 0 && !textEdit.text && prompt.text && !textEdit.inputMethodComposing
244 PropertyChanges { target: prompt; opacity: 0.6; }
250 from: "unfocused"; to: "focused";
252 SequentialAnimation {
253 PauseAnimation { duration: 60 }
254 NumberAnimation { target: prompt; properties: "opacity"; duration: 150 }
258 from: "focused"; to: "";
260 SequentialAnimation {
261 PauseAnimation { duration: 60 }
262 NumberAnimation { target: prompt; properties: "opacity"; duration: 100 }
269 enabled: !textEdit.activeFocus
272 anchors.margins: UI.TOUCH_EXPANSION_MARGIN
274 if (!textEdit.activeFocus) {
275 textEdit.forceActiveFocus();
277 // activate to preedit and/or move the cursor
278 var preeditDisabled = root.inputMethodHints &
279 root.__preeditDisabledMask
280 var injectionSucceeded = false;
281 var mappedMousePos = mapToItem(textEdit, mouseX, mouseY);
282 var newCursorPosition = textEdit.positionAt(mappedMousePos.x, mappedMousePos.y, TextInput.CursorOnCharacter);
284 && !TextAreaHelper.atSpace(newCursorPosition)
285 && newCursorPosition != textEdit.text.length
286 && !(newCursorPosition == 0 || TextAreaHelper.atSpace(newCursorPosition - 1))) {
287 injectionSucceeded = TextAreaHelper.injectWordToPreedit(newCursorPosition);
289 if (!injectionSucceeded) {
290 textEdit.cursorPosition=newCursorPosition;
299 // Exposed for the edit bubble
300 property alias preedit: inputMethodObserver.preedit
301 property alias preeditCursorPosition: inputMethodObserver.preeditCursorPosition
304 y: (UI.FIELD_DEFAULT_HEIGHT - font.pixelSize) / 2
305 width: parent.width - UI.PADDING_XLARGE * 2
307 font: root.platformStyle.textFont
308 color: root.platformStyle.textColor
310 selectedTextColor: root.platformStyle.selectedTextColor
311 selectionColor: root.platformStyle.selectionColor
312 mouseSelectionMode: TextInput.SelectWords
313 wrapMode: TextEdit.Wrap
314 persistentSelection: false
317 function updateMagnifierPosition(posX, posY) {
319 var magnifier = MagnifierPopup.popup;
320 var cursorHeight = textEdit.positionToRectangle(0,0).height;
321 var mappedPos = mapToItem(magnifier.parent, posX - magnifier.width / 2,
322 posY - magnifier.height / 2 - cursorHeight - 70);
324 magnifier.xCenter = mapToItem(magnifier.sourceItem, posX, 0).x;
325 magnifier.x = mappedPos.x;
326 if (-root.mapFromItem(magnifier.__rootElement(), 0,0).y - (posY - cursorHeight) < (magnifier.height / 1.5)) {
327 yAdjustment = Math.max(0,(magnifier.height / 1.5) + root.mapFromItem(magnifier.__rootElement(), 0,0).y - (posY - cursorHeight));
331 magnifier.yCenter = mapToItem(magnifier.sourceItem, 0, posY - cursorHeight + 50).y
332 magnifier.y = mappedPos.y + yAdjustment;
335 Component.onDestruction: {
336 Popup.close(textEdit);
340 if(root.activeFocus) {
341 TextAreaHelper.repositionFlickable(contentMovingAnimation);
344 if (textEdit.preedit == "" && Popup.isOpened(textEdit) && !Popup.isChangingInput())
345 Popup.close(textEdit);
349 target: TextAreaHelper.findFlickable(root.parent)
351 onContentYChanged: if (root.activeFocus) TextAreaHelper.filteredInputContextUpdate();
352 onContentXChanged: if (root.activeFocus) TextAreaHelper.filteredInputContextUpdate();
353 onMovementEnded: inputContext.update();
359 onSoftwareInputPanelVisibleChanged: {
361 TextAreaHelper.repositionFlickable(contentMovingAnimation);
364 onSoftwareInputPanelRectChanged: {
366 TextAreaHelper.repositionFlickable(contentMovingAnimation);
370 onCursorPositionChanged: {
371 if(!MagnifierPopup.isOpened() && activeFocus) {
372 TextAreaHelper.repositionFlickable(contentMovingAnimation)
375 if (MagnifierPopup.isOpened() &&
376 Popup.isOpened(textEdit)) {
377 Popup.close(textEdit);
378 } else if ((!mouseFilter.attemptToActivate ||
379 textEdit.cursorPosition == textEdit.text.length) &&
380 Popup.isOpened(textEdit)) {
381 Popup.close(textEdit);
383 textEdit.positionToRectangle(textEdit.cursorPosition));
387 onSelectedTextChanged: {
388 if (Popup.isOpened(textEdit) && !Popup.isChangingInput()) {
389 Popup.close(textEdit);
393 InputMethodObserver {
394 id: inputMethodObserver
397 if (Popup.isOpened(textEdit) && !Popup.isChangingInput()) {
398 Popup.close(textEdit);
407 onTriggered: TextAreaHelper.repositionFlickable(contentMovingAnimation)
411 id: contentMovingAnimation
414 easing.type: Easing.InOutCubic
420 anchors.leftMargin: UI.TOUCH_EXPANSION_MARGIN - UI.PADDING_XLARGE
421 anchors.rightMargin: UI.TOUCH_EXPANSION_MARGIN - UI.PADDING_MEDIUM
422 anchors.topMargin: UI.TOUCH_EXPANSION_MARGIN - (UI.FIELD_DEFAULT_HEIGHT - font.pixelSize) / 2
423 anchors.bottomMargin: UI.TOUCH_EXPANSION_MARGIN - (UI.FIELD_DEFAULT_HEIGHT - font.pixelSize) / 2
425 property bool attemptToActivate: false
426 property bool pressOnPreedit
428 property variant editBubblePosition: Qt.point(0,0)
431 var mousePosition = textEdit.positionAt(mouse.x,mouse.y,TextEdit.CursorOnCharacter);
432 pressOnPreedit = textEdit.cursorPosition==mousePosition
433 var preeditDisabled = root.inputMethodHints &
434 root.__preeditDisabledMask
436 attemptToActivate = !pressOnPreedit && !root.readOnly && !preeditDisabled && root.activeFocus &&
437 !(mousePosition == 0 || TextAreaHelper.atSpace(mousePosition - 1) || TextAreaHelper.atSpace(mousePosition));
438 mouse.filtered = true;
442 // possible pre-edit word have to be committed before selection
443 if (root.activeFocus || root.readOnly) {
445 parent.selectByMouse = true
446 attemptToActivate = false
451 // possible pre-edit word have to be commited before showing the magnifier
452 if ((root.text != "" || inputMethodObserver.preedit != "") && root.activeFocus) {
454 attemptToActivate = false
455 parent.selectByMouse = false
456 MagnifierPopup.open(root);
457 var magnifier = MagnifierPopup.popup;
458 parent.cursorPosition = parent.positionAt(mouse.x,mouse.y)
459 parent.updateMagnifierPosition(mouse.x,mouse.y)
460 root.z = Number.MAX_VALUE
465 if (MagnifierPopup.isOpened()) {
466 MagnifierPopup.close();
467 TextAreaHelper.repositionFlickable(contentMovingAnimation);
470 if (attemptToActivate)
471 inputContext.reset();
473 var newCursorPosition = textEdit.positionAt(mouse.x,mouse.y,TextEdit.CursorOnCharacter);
474 editBubblePosition = textEdit.positionToRectangle(newCursorPosition);
476 if (attemptToActivate) {
477 var beforeText = textEdit.text;
479 textEdit.cursorPosition = newCursorPosition;
480 var injectionSucceeded = false;
482 if (!TextAreaHelper.atSpace(newCursorPosition)
483 && newCursorPosition != textEdit.text.length) {
484 injectionSucceeded = TextAreaHelper.injectWordToPreedit(newCursorPosition);
486 if (injectionSucceeded) {
488 if (textEdit.preedit.length >=1 && textEdit.preedit.length <= 4)
489 editBubblePosition = textEdit.positionToRectangle(textEdit.cursorPosition);
491 textEdit.text=beforeText;
492 textEdit.cursorPosition=newCursorPosition;
494 attemptToActivate = false;
495 } else if (!parent.selectByMouse) {
496 if (!pressOnPreedit) inputContext.reset();
497 textEdit.cursorPosition = textEdit.positionAt(mouse.x,mouse.y,TextEdit.CursorOnCharacter);
499 parent.selectByMouse = false;
502 if (root.activeFocus && platformEnableEditBubble) {
503 if (textEdit.preedit.length == 0)
504 editBubblePosition = textEdit.positionToRectangle(textEdit.cursorPosition);
505 Popup.open(textEdit,editBubblePosition);
508 onMousePositionChanged: {
509 if (MagnifierPopup.isOpened() && !parent.selectByMouse) {
510 var pos = textEdit.positionAt (mouse.x,mouse.y)
511 var posNextLine = textEdit.positionAt (mouse.x, mouse.y + 1)
512 var posPrevLine = textEdit.positionAt (mouse.x, mouse.y - 1)
513 if (!(Math.abs(posNextLine - pos) > 1 ||
514 Math.abs(posPrevLine - pos) > 1)) {
515 parent.cursorPosition = pos
517 parent.updateMagnifierPosition(mouse.x,mouse.y);
521 // possible pre-edit word have to be committed before selection
523 parent.selectByMouse = true
524 attemptToActivate = false
533 anchors.margins: UI.TOUCH_EXPANSION_MARGIN
534 enabled: root.activeFocus
537 if (Popup.isOpened(textEdit) && ((mouseX > Popup.geometry().left && mouseX < Popup.geometry().right) &&
538 (mouseY > Popup.geometry().top && mouseY < Popup.geometry().bottom))) {
542 root.parent.focus = true;