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 "UIConstants.js" as UI
44 import "EditBubble.js" as Popup
45 import "TextAreaHelper.js" as TextAreaHelper
46 import "Magnifier.js" as MagnifierPopup
51 property alias text: textInput.text
52 property alias placeholderText: prompt.text
54 property alias inputMethodHints: textInput.inputMethodHints
55 property alias font: textInput.font
56 property alias cursorPosition: textInput.cursorPosition
57 property alias maximumLength: textInput.maximumLength
58 property alias readOnly: textInput.readOnly
59 property alias acceptableInput: textInput.acceptableInput
60 property alias inputMask: textInput.inputMask
61 property alias validator: textInput.validator
63 property alias selectedText: textInput.selectedText
64 property alias selectionStart: textInput.selectionStart
65 property alias selectionEnd: textInput.selectionEnd
67 property alias echoMode: textInput.echoMode // ### TODO: declare own enum { Normal, Password }
69 property bool errorHighlight: !acceptableInput
70 // Property enableSoftwareInputPanel is DEPRECATED
71 property alias enableSoftwareInputPanel: textInput.activeFocusOnPress
73 property Item platformSipAttributes
75 property bool platformEnableEditBubble: true
77 property Item platformStyle: TextFieldStyle {}
79 property alias style: root.platformStyle
81 property Component customSoftwareInputPanel
83 property Component platformCustomSoftwareInputPanel
85 property alias platformPreedit: inputMethodObserver.preedit
89 onPlatformSipAttributesChanged: {
90 platformSipAttributes.registerInputElement(textInput)
93 onCustomSoftwareInputPanelChanged: {
94 console.log("TextField's property customSoftwareInputPanel is deprecated. Use property platformCustomSoftwareInputPanel instead.")
95 platformCustomSoftwareInputPanel = customSoftwareInputPanel
98 onPlatformCustomSoftwareInputPanelChanged: {
99 textInput.activeFocusOnPress = platformCustomSoftwareInputPanel == null
109 target: platformWindow
112 if(platformWindow.active) {
115 if (platformCustomSoftwareInputPanel != null) {
116 platformOpenSoftwareInputPanel();
118 inputContext.simulateSipOpen();
120 repositionTimer.running = true;
125 platformCloseSoftwareInputPanel();
126 Popup.close(textInput);
131 onAnimatingChanged: {
132 if (!platformWindow.animating && root.activeFocus) {
133 TextAreaHelper.repositionFlickable(contentMovingAnimation);
147 function select(start, end) {
148 textInput.select(start, end)
151 function selectAll() {
152 textInput.selectAll()
155 function selectWord() {
156 textInput.selectWord()
159 function positionAt(x) {
160 var p = mapToItem(textInput, x, 0);
161 return textInput.positionAt(p.x)
164 function positionToRectangle(pos) {
165 var rect = textInput.positionToRectangle(pos)
166 rect.x = mapFromItem(textInput, rect.x, 0).x
170 // ensure propagation of forceActiveFocus
171 function forceActiveFocus() {
172 textInput.forceActiveFocus()
175 function closeSoftwareInputPanel() {
176 console.log("TextField's function closeSoftwareInputPanel is deprecated. Use function platformCloseSoftwareInputPanel instead.")
177 platformCloseSoftwareInputPanel()
180 function platformCloseSoftwareInputPanel() {
181 inputContext.simulateSipClose();
182 if (inputContext.customSoftwareInputPanelVisible) {
183 inputContext.customSoftwareInputPanelVisible = false
184 inputContext.customSoftwareInputPanelComponent = null
185 inputContext.customSoftwareInputPanelTextField = null
187 textInput.closeSoftwareInputPanel();
191 function openSoftwareInputPanel() {
192 console.log("TextField's function openSoftwareInputPanel is deprecated. Use function platformOpenSoftwareInputPanel instead.")
193 platformOpenSoftwareInputPanel()
196 function platformOpenSoftwareInputPanel() {
197 inputContext.simulateSipOpen();
198 if (platformCustomSoftwareInputPanel != null && !inputContext.customSoftwareInputPanelVisible) {
199 inputContext.customSoftwareInputPanelTextField = root
200 inputContext.customSoftwareInputPanelComponent = platformCustomSoftwareInputPanel
201 inputContext.customSoftwareInputPanelVisible = true
203 textInput.openSoftwareInputPanel();
208 property bool __expanding: true // Layout hint used but ToolBarLayout
209 property int __preeditDisabledMask: Qt.ImhHiddenText|
210 Qt.ImhNoPredictiveText|
212 Qt.ImhFormattedNumbersOnly|
213 Qt.ImhDialableCharactersOnly|
214 Qt.ImhEmailCharactersOnly|
215 Qt.ImhUrlCharactersOnly
217 implicitWidth: platformStyle.defaultWidth
218 implicitHeight: UI.FIELD_DEFAULT_HEIGHT
220 onActiveFocusChanged: {
223 if (platformCustomSoftwareInputPanel != null) {
224 platformOpenSoftwareInputPanel();
226 inputContext.simulateSipOpen();
229 repositionTimer.running = true;
231 platformCloseSoftwareInputPanel();
232 Popup.close(textInput);
240 source: errorHighlight?
241 platformStyle.backgroundError:
243 platformStyle.backgroundDisabled:
244 textInput.activeFocus?
245 platformStyle.backgroundSelected:
246 platformStyle.background
249 border.left: root.platformStyle.backgroundCornerMargin; border.top: root.platformStyle.backgroundCornerMargin
250 border.right: root.platformStyle.backgroundCornerMargin; border.bottom: root.platformStyle.backgroundCornerMargin
256 anchors {verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right}
257 anchors.leftMargin: root.platformStyle.paddingLeft
258 anchors.rightMargin: root.platformStyle.paddingRight
259 anchors.verticalCenterOffset: root.platformStyle.baselineOffset
261 font: root.platformStyle.textFont
262 color: root.platformStyle.promptTextColor
263 elide: Text.ElideRight
265 // opacity for default state
271 // memory allocation optimization: cursorPosition is checked to minimize displayText evaluations
272 when: !root.activeFocus && textInput.cursorPosition == 0 && !textInput.text && prompt.text && !textInput.inputMethodComposing
273 PropertyChanges { target: prompt; opacity: 1.0; }
277 // memory allocation optimization: cursorPosition is checked to minimize displayText evaluations
278 when: root.activeFocus && textInput.cursorPosition == 0 && !textInput.text && prompt.text && !textInput.inputMethodComposing
279 PropertyChanges { target: prompt; opacity: 0.6; }
285 from: "unfocused"; to: "focused";
287 SequentialAnimation {
288 PauseAnimation { duration: 60 }
289 NumberAnimation { target: prompt; properties: "opacity"; duration: 150 }
293 from: "focused"; to: "";
295 SequentialAnimation {
296 PauseAnimation { duration: 60 }
297 NumberAnimation { target: prompt; properties: "opacity"; duration: 100 }
304 enabled: !textInput.activeFocus
307 anchors.margins: UI.TOUCH_EXPANSION_MARGIN
309 if (!textInput.activeFocus) {
310 textInput.forceActiveFocus();
312 // activate to preedit and/or move the cursor
313 var preeditDisabled = root.inputMethodHints &
314 root.__preeditDisabledMask
315 var injectionSucceeded = false;
316 var newCursorPosition = textInput.positionAt(mapToItem(textInput, mouseX, mouseY).x,TextInput.CursorOnCharacter);
318 && !TextAreaHelper.atSpace(newCursorPosition)
319 && newCursorPosition != textInput.text.length
320 && !(newCursorPosition == 0 || TextAreaHelper.atSpace(newCursorPosition - 1))) {
321 injectionSucceeded = TextAreaHelper.injectWordToPreedit(newCursorPosition);
323 if (!injectionSucceeded) {
324 textInput.cursorPosition=newCursorPosition;
333 property alias preedit: inputMethodObserver.preedit
334 property alias preeditCursorPosition: inputMethodObserver.preeditCursorPosition
336 anchors {verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right}
337 anchors.leftMargin: root.platformStyle.paddingLeft
338 anchors.rightMargin: root.platformStyle.paddingRight
339 anchors.verticalCenterOffset: root.platformStyle.baselineOffset
341 passwordCharacter: "\u2022"
342 font: root.platformStyle.textFont
343 color: root.platformStyle.textColor
345 selectedTextColor: root.platformStyle.selectedTextColor
346 selectionColor: root.platformStyle.selectionColor
347 mouseSelectionMode: TextInput.SelectWords
350 onAccepted: { root.accepted() }
352 Component.onDestruction: {
353 Popup.close(textInput);
357 target: TextAreaHelper.findFlickable(root.parent)
359 onContentYChanged: if (root.activeFocus) TextAreaHelper.filteredInputContextUpdate();
360 onContentXChanged: if (root.activeFocus) TextAreaHelper.filteredInputContextUpdate();
361 onMovementEnded: inputContext.update();
367 onSoftwareInputPanelRectChanged: {
369 repositionTimer.running = true
375 if(root.activeFocus) {
376 TextAreaHelper.repositionFlickable(contentMovingAnimation)
379 if (Popup.isOpened(textInput) && !Popup.isChangingInput())
380 Popup.close(textInput);
383 onCursorPositionChanged: {
384 if (MagnifierPopup.isOpened() &&
386 Popup.close(textInput);
387 } else if ((!mouseFilter.attemptToActivate ||
388 textInput.cursorPosition == textInput.text.length) &&
389 Popup.isOpened(textInput) &&
390 !Popup.isChangingInput()) {
391 Popup.close(textInput);
392 Popup.open(textInput,
393 textInput.positionToRectangle(textInput.cursorPosition));
397 onSelectedTextChanged: {
398 if (Popup.isOpened(textInput) && !Popup.isChangingInput()) {
399 Popup.close(textInput);
403 InputMethodObserver {
404 id: inputMethodObserver
407 if(root.activeFocus) {
408 TextAreaHelper.repositionFlickable(contentMovingAnimation)
411 if (Popup.isOpened(textInput) && !Popup.isChangingInput()) {
412 Popup.close(textInput);
421 TextAreaHelper.repositionFlickable(contentMovingAnimation)
426 id: contentMovingAnimation
429 easing.type: Easing.InOutCubic
435 anchors.leftMargin: UI.TOUCH_EXPANSION_MARGIN - root.platformStyle.paddingLeft
436 anchors.rightMargin: UI.TOUCH_EXPANSION_MARGIN - root.platformStyle.paddingRight
437 anchors.topMargin: UI.TOUCH_EXPANSION_MARGIN - ((root.height - parent.height) / 2)
438 anchors.bottomMargin: UI.TOUCH_EXPANSION_MARGIN - ((root.height - parent.height) / 2)
440 property bool attemptToActivate: false
441 property bool pressOnPreedit: false
442 property int oldCursorPosition: 0
444 property variant editBubblePosition: Qt.point(0,0)
447 var mousePosition = textInput.positionAt(mouse.x,TextInput.CursorOnCharacter);
448 pressOnPreedit = textInput.cursorPosition==mousePosition
449 oldCursorPosition = textInput.cursorPosition;
450 var preeditDisabled = root.inputMethodHints &
451 root.__preeditDisabledMask
453 attemptToActivate = !pressOnPreedit && !root.readOnly && !preeditDisabled && root.activeFocus &&
454 !(mousePosition == 0 || TextAreaHelper.atSpace(mousePosition - 1) || TextAreaHelper.atSpace(mousePosition));
455 mouse.filtered = true;
458 onDelayedPressSent: {
459 if (textInput.preedit) {
460 textInput.cursorPosition = oldCursorPosition;
465 // possible pre-edit word have to be commited before selection
466 if (root.activeFocus || root.readOnly) {
468 parent.selectByMouse = true
469 attemptToActivate = false
474 // possible pre-edit word have to be commited before showing the magnifier
475 if ((root.text != "" || inputMethodObserver.preedit != "") && root.activeFocus) {
477 attemptToActivate = false
478 MagnifierPopup.open(root);
479 var magnifier = MagnifierPopup.popup;
480 var cursorPos = textInput.positionToRectangle(0);
481 var mappedPosMf = mapFromItem(parent,mouse.x,cursorPos.y+cursorPos.height);
482 magnifier.xCenter = mapToItem(magnifier.sourceItem,mappedPosMf.x,0).x;
483 var mappedPos = mapToItem(magnifier.parent, mappedPosMf.x - magnifier.width / 2,
484 textInput.y - 120 - UI.MARGIN_XLARGE - (height / 2));
485 var yAdjustment = -mapFromItem(magnifier.__rootElement(), 0, 0).y < magnifier.height / 2.5 ? magnifier.height / 2.5 + mapFromItem(magnifier.__rootElement(), 0,0).y : 0
486 magnifier.x = mappedPos.x;
487 magnifier.y = mappedPos.y + yAdjustment;
488 magnifier.yCenter = mapToItem(magnifier.sourceItem,0,mappedPosMf.y).y;
489 parent.cursorPosition = textInput.positionAt(mouse.x)
494 if (MagnifierPopup.isOpened()) {
495 MagnifierPopup.close();
498 if (attemptToActivate)
499 inputContext.reset();
501 var newCursorPosition = textInput.positionAt(mouse.x,TextInput.CursorOnCharacter);
502 editBubblePosition = textInput.positionToRectangle(newCursorPosition);
504 if (attemptToActivate) {
505 var beforeText = textInput.text;
507 textInput.cursorPosition = newCursorPosition;
508 var injectionSucceeded = false;
510 if (!TextAreaHelper.atSpace(newCursorPosition)
511 && newCursorPosition != textInput.text.length) {
512 injectionSucceeded = TextAreaHelper.injectWordToPreedit(newCursorPosition);
514 if (injectionSucceeded) {
516 if (textInput.preedit.length >=1 && textInput.preedit.length <= 4)
517 editBubblePosition = textInput.positionToRectangle(textInput.cursorPosition+1)
519 textInput.text=beforeText;
520 textInput.cursorPosition=newCursorPosition;
522 } else if (!parent.selectByMouse) {
523 if (!pressOnPreedit) inputContext.reset();
524 textInput.cursorPosition = textInput.positionAt(mouse.x,TextInput.CursorOnCharacter);
526 parent.selectByMouse = false;
530 if (root.activeFocus && platformEnableEditBubble) {
531 if (textInput.preedit.length == 0)
532 editBubblePosition = textInput.positionToRectangle(textInput.cursorPosition);
533 Popup.open(textInput,editBubblePosition);
536 attemptToActivate = false
539 onMousePositionChanged: {
540 if (MagnifierPopup.isOpened() && !parent.selectByMouse) {
541 textInput.cursorPosition = textInput.positionAt(mouse.x)
542 var magnifier = MagnifierPopup.popup;
543 var mappedPosMf = mapFromItem(parent,mouse.x,0);
544 var mappedPos = mapToItem(magnifier.parent,mappedPosMf.x - magnifier.width / 2.0, 0);
545 magnifier.xCenter = mapToItem(magnifier.sourceItem,mappedPosMf.x,0).x;
546 magnifier.x = mappedPos.x;
551 // possible pre-edit word have to be commited before selection
553 parent.selectByMouse = true
554 attemptToActivate = false
561 anchors.margins: UI.TOUCH_EXPANSION_MARGIN
562 enabled: textInput.activeFocus
564 if (Popup.isOpened(textInput) && ((mouseX > Popup.geometry().left && mouseX < Popup.geometry().right) &&
565 (mouseY > Popup.geometry().top && mouseY < Popup.geometry().bottom))) {
568 root.parent.focus = true;