added android components
[mardrone] / mardrone / imports / com / nokia / android.1.1 / TextArea.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
44 FocusScopeItem {
45     id: root
46
47     // Common public API
48     property alias font: textEdit.font
49     property alias cursorPosition: textEdit.cursorPosition
50     property alias horizontalAlignment: textEdit.horizontalAlignment
51     property alias inputMethodHints: textEdit.inputMethodHints
52     property alias verticalAlignment: textEdit.verticalAlignment
53     property alias readOnly: textEdit.readOnly
54     property alias selectedText: textEdit.selectedText
55     property alias selectionEnd: textEdit.selectionEnd
56     property alias selectionStart: textEdit.selectionStart
57     property alias text: textEdit.text
58     property alias textFormat: textEdit.textFormat
59     property alias wrapMode: textEdit.wrapMode
60     property bool errorHighlight: false
61
62     function copy() {
63         textEdit.copy()
64     }
65
66     function paste() {
67         textEdit.paste()
68     }
69
70     function cut() {
71         textEdit.cut()
72     }
73
74     function select(start, end) {
75         textEdit.select(start, end)
76     }
77
78     function selectAll() {
79         textEdit.selectAll()
80     }
81
82     function selectWord() {
83         textEdit.selectWord()
84     }
85
86     function positionAt(x, y) {
87         var p = mapToItem(textEdit, x, y);
88         return textEdit.positionAt(p.x, p.y)
89     }
90
91     function positionToRectangle(pos) {
92         var rect = textEdit.positionToRectangle(pos)
93         var point = mapFromItem(textEdit, rect.x, rect.y)
94         rect.x = point.x; rect.y = point.y
95         return rect;
96     }
97
98     function openSoftwareInputPanel() {
99         textEdit.openSoftwareInputPanel()
100     }
101
102     function closeSoftwareInputPanel() {
103         textEdit.closeSoftwareInputPanel()
104     }
105
106     // API extensions
107     property alias placeholderText: placeholder.text
108     // TODO: Refactor implicit size when following bugs are resolved
109     // http://bugreports.qt.nokia.com/browse/QTBUG-14957
110     // http://bugreports.qt.nokia.com/browse/QTBUG-16665
111     // http://bugreports.qt.nokia.com/browse/QTBUG-16710 (fixed in Qt 4.7.2)
112     // http://bugreports.qt.nokia.com/browse/QTBUG-12305 (fixed in QtQuick1.1)
113     property real platformMaxImplicitWidth: -1
114     property real platformMaxImplicitHeight: -1
115     property bool platformInverted: false
116     property bool enabled: true // overriding due to QTBUG-15797 and related bugs
117
118     implicitWidth: {
119         var preferredWidth = placeholder.visible ? placeholder.model.paintedWidth
120                                                  : flick.contentWidth
121         preferredWidth = Math.max(preferredWidth, privy.minImplicitWidth)
122         preferredWidth += container.horizontalMargins
123         if (root.platformMaxImplicitWidth >= 0)
124             return Math.min(preferredWidth, root.platformMaxImplicitWidth)
125         return preferredWidth
126     }
127
128     implicitHeight: {
129         var preferredHeight = placeholder.visible ? placeholder.model.paintedHeight
130                                                   : flick.contentHeight
131         preferredHeight += container.verticalMargins
132         // layout spec gives minimum height (textFieldHeight) which includes required padding
133         preferredHeight = Math.max(privateStyle.textFieldHeight, preferredHeight)
134         if (root.platformMaxImplicitHeight >= 0)
135             return Math.min(preferredHeight, root.platformMaxImplicitHeight)
136         return preferredHeight
137     }
138
139     onWidthChanged: {
140         // Detect when a width has been explicitly set. Needed to determine if the TextEdit should
141         // grow horizontally or wrap. I.e. in determining the model's width. There's no way to get
142         // notified of having an explicit width set. Therefore it's polled in widthChanged.
143         privy.widthExplicit = root.widthExplicit()
144     }
145
146     Connections {
147          target: screen
148          onCurrentOrientationChanged: {
149              delayedEnsureVisible.start()
150              fade.start()
151              scroll.start()
152          }
153     }
154
155     QtObject {
156         id: privy
157         // TODO: More consistent minimum width for empty TextArea than 20 * " " on current font?
158         property real minImplicitWidth: privateStyle.textWidth("                    ", textEdit.font)
159         property bool widthExplicit: false
160         property bool wrap: privy.widthExplicit || root.platformMaxImplicitWidth >= 0
161         property real wrapWidth: privy.widthExplicit ? root.width - container.horizontalMargins
162                                                      : root.platformMaxImplicitWidth - container.horizontalMargins
163         function bg_postfix() {
164             if (root.errorHighlight)
165                 return "error"
166             else if (root.readOnly || !root.enabled)
167                 return "uneditable"
168             else if (textEdit.activeFocus)
169                 return "highlighted"
170             else
171                 return "editable"
172         }
173     }
174
175     BorderImage {
176         id: background
177         anchors.fill: parent
178         source: privateStyle.imagePath("qtg_fr_textfield_" + privy.bg_postfix(), root.platformInverted)
179         border {
180             left: platformStyle.borderSizeMedium
181             top: platformStyle.borderSizeMedium
182             right: platformStyle.borderSizeMedium
183             bottom: platformStyle.borderSizeMedium
184         }
185         smooth: true
186     }
187
188     Item {
189         id: container
190
191         property real horizontalMargins:   container.anchors.leftMargin
192                                          + container.anchors.rightMargin
193                                          + flick.anchors.leftMargin
194                                          + flick.anchors.rightMargin
195
196         property real verticalMargins:  container.anchors.topMargin
197                                       + container.anchors.bottomMargin
198                                       + flick.anchors.topMargin
199                                       + flick.anchors.bottomMargin
200
201         anchors {
202             fill: parent
203             leftMargin: platformStyle.paddingSmall; rightMargin: platformStyle.paddingSmall
204             topMargin: platformStyle.paddingMedium; bottomMargin: platformStyle.paddingMedium
205         }
206
207         clip: true
208
209         // TODO: Should placeholder also be scrollable?
210         Text {
211             id: placeholder
212             objectName: "placeholder"
213
214             // TODO: See TODO: Refactor implicit size...
215             property variant model: Text {
216                 font: textEdit.font
217                 text: placeholder.text
218                 visible: false
219                 wrapMode: textEdit.wrapMode
220                 horizontalAlignment: textEdit.horizontalAlignment
221                 verticalAlignment: textEdit.verticalAlignment
222                 opacity: 0
223
224                 Binding {
225                     when: privy.wrap
226                     target: placeholder.model
227                     property: "width"
228                     value: privy.wrapWidth
229                 }
230             }
231
232             color: root.platformInverted ? platformStyle.colorNormalMidInverted
233                                          : platformStyle.colorNormalMid
234             font: textEdit.font
235             horizontalAlignment: textEdit.horizontalAlignment
236             verticalAlignment: textEdit.verticalAlignment
237             visible: {
238                 if (text && (textEdit.model.paintedWidth == 0 && textEdit.model.paintedHeight <= textEdit.cursorRectangle.height))
239                     return (readOnly || !textEdit.activeFocus)
240                 else
241                     return false
242             }
243             wrapMode: textEdit.wrapMode
244             x: flick.x; y: flick.y
245             height: flick.height; width: flick.width
246         }
247
248         Flickable {
249             id: flick
250
251             property real tiny: Math.round(platformStyle.borderSizeMedium / 2)
252
253             function ensureVisible(rect) {
254                 if (Math.round(contentX) > Math.round(rect.x))
255                     contentX = rect.x
256                 else if (Math.round(contentX + width) < Math.round(rect.x + rect.width))
257                     contentX = rect.x + rect.width - width
258
259                 if (Math.round(contentY) > Math.round(rect.y))
260                     contentY = rect.y
261                 else if (Math.round(contentY + height) < Math.round(rect.y + rect.height))
262                      contentY = rect.y + rect.height - height
263             }
264
265             anchors {
266                 fill: parent
267                 leftMargin: tiny
268                 rightMargin: tiny
269                 topMargin: tiny / 2
270                 bottomMargin: tiny / 2
271             }
272
273             boundsBehavior: Flickable.StopAtBounds
274             contentHeight: textEdit.model.paintedHeight
275             contentWidth: textEdit.model.paintedWidth +
276                          (textEdit.wrapMode == TextEdit.NoWrap ? textEdit.cursorRectangle.width : 0)
277             interactive: root.enabled
278
279             onHeightChanged: {
280                 if(textEdit.cursorVisible || textEdit.cursorPosition == textEdit.selectionEnd)
281                     ensureVisible(textEdit.cursorRectangle)
282             }
283
284             onWidthChanged: {
285                 if(textEdit.cursorVisible || textEdit.cursorPosition == textEdit.selectionEnd)
286                     ensureVisible(textEdit.cursorRectangle)
287             }
288
289             TextEdit {
290                 id: textEdit
291                 objectName: "textEdit"
292
293                 // TODO: See TODO: Refactor implicit size...
294                 property variant model: TextEdit {
295                     font: textEdit.font
296                     text: textEdit.text
297                     horizontalAlignment: textEdit.horizontalAlignment
298                     verticalAlignment: textEdit.verticalAlignment
299                     wrapMode: textEdit.wrapMode
300                     visible: false
301                     opacity: 0
302
303                     // In Wrap mode, if the width is bound the text will always get wrapped at the
304                     // set width. If the width is not bound the text won't get wrapped but
305                     // paintedWidth will increase.
306                     Binding {
307                         when: privy.wrap
308                         target: textEdit.model
309                         property: "width"
310                         value: privy.wrapWidth
311                     }
312                 }
313                 activeFocusOnPress: false
314                 enabled: root.enabled
315                 focus: true
316                 font { family: platformStyle.fontFamilyRegular; pixelSize: platformStyle.fontSizeMedium }
317                 color: root.platformInverted ? platformStyle.colorNormalLightInverted
318                                              : platformStyle.colorNormalDark
319                 cursorVisible: activeFocus && !selectedText
320                 selectedTextColor: root.platformInverted ? platformStyle.colorNormalDarkInverted
321                                                          : platformStyle.colorNormalLight
322                 selectionColor: root.platformInverted ? platformStyle.colorTextSelectionInverted
323                                                       : platformStyle.colorTextSelection
324                 textFormat: TextEdit.AutoText
325                 wrapMode: TextEdit.Wrap
326                 height: flick.height
327                 width: flick.width
328                 // TODO: Make bug report?
329                 // Called too early (before TextEdit size is adjusted) delay ensureVisible call a bit
330                 onCursorRectangleChanged: delayedEnsureVisible.start()
331                 onActiveFocusChanged: {
332                     if (activeFocus) {
333                         horizontal.flash()
334                         vertical.flash()
335                     }
336                 }
337                 onEnabledChanged: {
338                     if (!enabled) {
339                         deselect()
340                         // De-focusing requires setting focus elsewhere, in this case editor's parent
341                         if (root.parent)
342                             root.parent.forceActiveFocus()
343                     }
344                 }
345
346                 Keys.forwardTo: touchController
347
348                 TextTouchController {
349                     id: touchController
350
351                     //  selection handles require touch area geometry to differ from TextEdit's geometry
352                     anchors {
353                         top: editor.top; topMargin: -container.verticalMargins / 2
354                         left: editor.left; leftMargin: -container.horizontalMargins / 2
355                     }
356                     height: Math.max(root.height, flick.contentHeight + container.verticalMargins * 2)
357                     width: Math.max(root.width, flick.contentWidth + container.horizontalMargins * 2)
358                     editorScrolledX: flick.contentX - (container.horizontalMargins / 2)
359                     editorScrolledY: flick.contentY - (container.verticalMargins / 2)
360                     copyEnabled: textEdit.selectedText
361                     cutEnabled: !textEdit.readOnly && textEdit.selectedText
362                     platformInverted: root.platformInverted
363                     Component.onCompleted: flick.movementEnded.connect(touchController.flickEnded)
364                     Connections { target: screen; onCurrentOrientationChanged: touchController.updateGeometry() }
365                     Connections {
366                         target: textEdit
367                         onHeightChanged: touchController.updateGeometry()
368                         onWidthChanged: touchController.updateGeometry()
369                         onHorizontalAlignmentChanged: touchController.updateGeometry()
370                         onVerticalAlignmentChanged: touchController.updateGeometry()
371                         onWrapModeChanged: touchController.updateGeometry()
372                         onFontChanged: touchController.updateGeometry()
373                     }
374                 }
375             }
376
377             PropertyAnimation { id: fade; target: textEdit; property: "opacity"; from: 0; to: 1; duration: 250 }
378             PropertyAnimation { id: scroll; target: flick; property: "contentY"; duration: 250 }
379         }
380
381         ScrollBar {
382             id: vertical
383             anchors {
384                 top: flick.top;
385                 topMargin: -flick.tiny / 2;
386                 left: flick.right;
387             }
388             flickableItem: flick
389             interactive: false
390             orientation: Qt.Vertical
391             height: container.height
392             platformInverted: root.platformInverted
393         }
394
395         ScrollBar {
396             id: horizontal
397             anchors {
398                 left: flick.left;
399                 leftMargin: -flick.tiny;
400                 bottom: container.bottom;
401                 bottomMargin: -flick.tiny / 2
402                 rightMargin: vertical.opacity ? vertical.width : 0
403             }
404             flickableItem: flick
405             interactive: false
406             orientation: Qt.Horizontal
407             width: container.width
408             platformInverted: root.platformInverted
409         }
410
411         Timer {
412             id: delayedEnsureVisible
413             interval: 1
414             onTriggered: flick.ensureVisible(textEdit.cursorRectangle)
415         }
416     }
417 }