components for android added
[mardrone] / mardrone / imports / com / nokia / meego / TextAreaHelper.js
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 function findFlickable(component) {
42     var nextParent = component
43     var flickableItem = null
44     while(nextParent) {
45         if(nextParent.flicking !== undefined && nextParent.flickableDirection !== undefined)
46             flickableItem = nextParent
47
48         nextParent = nextParent.parent
49     }
50     if (flickableItem) return flickableItem
51     return null
52 }
53
54 function animateContentY(animation, flickable, newContentY) {
55     animation.target = flickable
56     animation.to = newContentY
57     animation.running = true
58 }
59
60 function locateFlickableY(flickable) {
61     switch(screen.currentOrientation) {
62     case Screen.Landscape:
63         return flickable.mapToItem(null, flickable.x, flickable.y).y
64
65     case Screen.LandscapeInverted:
66         return screen.displayHeight - flickable.mapToItem(null, flickable.x, flickable.y).y
67
68     case Screen.Portrait:
69         return flickable.mapToItem(null, flickable.x, flickable.y).x
70
71     case Screen.PortraitInverted:
72         return screen.displayWidth - flickable.mapToItem(null, flickable.x, flickable.y).x
73     }
74 }
75
76 function getMargin() {
77     switch(screen.currentOrientation) {
78     case Screen.Landscape:
79     case Screen.LandscapeInverted:
80         return 40
81     case Screen.Portrait:
82     case Screen.PortraitInverted:
83         return 48
84     }
85
86     return 0
87 }
88
89 function repositionFlickable(animation) {
90     inputContext.updateMicroFocus()
91     var mf = inputContext.microFocus
92
93     if(mf.x == -1 && mf.y == -1)
94         return
95
96     var object = findFlickable(parent)
97
98     if(object){
99         var flickable = object
100
101         // Specifies area from bottom and top when repositioning should be triggered
102         var margin = getMargin()
103         var newContentY = flickable.contentY
104         var flickableY = locateFlickableY(flickable)
105
106         switch(screen.currentOrientation) {
107         case Screen.Landscape:
108             if(flickableY + flickable.height  - mf.height - margin < mf.y) {
109                 // Find dY just to make textfield visible
110                 var dY = mf.y - flickableY - flickable.height
111                 // Center textfield
112                 dY += flickable.height / 2
113                 newContentY += dY
114             } else if(flickableY + margin > mf.y) {
115                 var dY = flickableY - mf.y
116                 dY += flickable.height / 2
117                 newContentY -= dY
118             }
119
120             break
121
122         case Screen.LandscapeInverted:
123             // In inverted screen we need to compensate for the focus height
124             var invertedMfY = screen.displayHeight - mf.y - mf.height
125
126             if(flickableY + flickable.height - mf.height - margin < invertedMfY) {
127                 var dY = invertedMfY - flickableY - flickable.height
128                 dY += flickable.height / 2 + mf.height / 2
129             } else if(flickableY + margin > invertedMfY){
130                 var dY = flickableY - invertedMfY
131                 dY += flickable.height / 2 - mf.height / 2
132                 newContentY -= dY
133             }
134
135             break
136
137         case Screen.Portrait:
138             if(flickableY + flickable.height - mf.width - margin < mf.x) {
139                 var dY = mf.x - flickableY - flickable.height
140                 dY += flickable.height / 2
141                 newContentY += dY
142             } else if(flickableY + margin > mf.x){
143                 var dY = flickableY - mf.x
144                 dY += flickable.height / 2
145                 newContentY -= dY
146             }
147
148             break
149
150         case Screen.PortraitInverted:
151             var invertedMfX = screen.displayWidth - mf.x - mf.width
152
153             if(flickableY + flickable.height - mf.width - margin < invertedMfX) {
154                 var dY = invertedMfX - flickableY - flickable.height + mf.height
155                 dY += flickable.height / 2 + mf.height
156                 newContentY += dY
157             } else if(flickableY + margin > invertedMfX){
158                 var dY = flickableY - invertedMfX
159                 dY += flickable.height / 2 - mf.height
160                 newContentY -= dY
161             }
162
163             break
164         }
165
166         // If overpanned, set contentY to max possible value (reached bottom)
167         if(newContentY > flickable.contentHeight - flickable.height)
168             newContentY = flickable.contentHeight - flickable.height
169
170         // If overpanned, set contentY to min possible value (reached top)
171         if(newContentY < 0)
172             newContentY = 0
173
174         if(newContentY != flickable.contentY) {
175             animateContentY(animation, flickable, newContentY)
176         }
177     }
178 }
179
180 function injectWordToPreedit(newCursorPosition) {
181     var preeditStart = previousWordStart(newCursorPosition);
182     var preeditEnd = nextWordEnd(newCursorPosition);
183
184     // copy word to preedit text
185     var preeditText = root.text.substring(preeditStart,preeditEnd);
186
187     // inject preedit
188     cursorPosition = preeditStart;
189
190     var eventCursorPosition = newCursorPosition-preeditStart;
191
192     return inputContext.setPreeditText(preeditText, eventCursorPosition, 0, preeditText.length);
193 }
194
195 function previousWordStart(pos) {
196     var ret = pos;
197
198     if (ret && atWordSeparator(ret - 1)) {
199         ret--;
200         while (ret && atWordSeparator(ret - 1))
201             ret--;
202     } else {
203         while (ret && !atSpace(ret - 1) && !atWordSeparator(ret - 1))
204             ret--;
205     }
206
207     return ret;
208 }
209
210 function nextWordEnd(pos) {
211     var ret = pos;
212     var len = root.text.length;
213
214     if (ret < len && atWordSeparator(ret)) {
215         ret++;
216         while (ret < len && atWordSeparator(ret))
217             ret++;
218     } else {
219         while (ret < len && !atSpace(ret) && !atWordSeparator(ret))
220             ret++;
221     }
222
223     return ret;
224 }
225
226 function atSpace(pos) {
227     var c = root.text.charAt(pos);
228     return c == ' '
229            || c == '\t'
230            || c == '\n'
231            ;
232 }
233
234 function atWordSeparator(pos) {
235     switch (root.text.charAt(pos)) {
236     case '.':
237     case ',':
238     case '?':
239     case '!':
240     case '@':
241     case '#':
242     case '$':
243     case ':':
244     case ';':
245     case '-':
246     case '<':
247     case '>':
248     case '[':
249     case ']':
250     case '(':
251     case ')':
252     case '{':
253     case '}':
254     case '=':
255     case '/':
256     case '+':
257     case '%':
258     case '&':
259     case '^':
260     case '*':
261     case '\'':
262     case '"':
263     case '`':
264     case '~':
265     case '|':
266         return true;
267     default:
268         return false;
269     }
270 }
271
272 var MIN_UPDATE_INTERVAL = 30
273 var lastUpdateTime
274 function filteredInputContextUpdate() {
275     if (Date.now() - lastUpdateTime > MIN_UPDATE_INTERVAL || !lastUpdateTime) {
276         inputContext.update();
277         lastUpdateTime = Date.now();
278     }
279 }