|
1 /* |
|
2 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: |
|
15 * |
|
16 */ |
|
17 package javax.microedition.lcdui; |
|
18 |
|
19 import java.util.Timer; |
|
20 import java.util.TimerTask; |
|
21 |
|
22 import org.eclipse.swt.SWT; |
|
23 |
|
24 /** |
|
25 * Responsible for implementing interaction in DefaultFormLayoutPolicy. |
|
26 */ |
|
27 class DefaultFormInteraction extends DefaultFormLayoutPolicy { |
|
28 |
|
29 private static final int NO_DIRECTION = -1; |
|
30 |
|
31 private Item currentSelectedItem; |
|
32 |
|
33 private int direction = NO_DIRECTION; |
|
34 |
|
35 private boolean mousePressed; |
|
36 |
|
37 private LayoutObject currentlyUnderMouse; |
|
38 |
|
39 /** |
|
40 * Constructor. |
|
41 * |
|
42 * @param form where DFLP is applied. |
|
43 */ |
|
44 DefaultFormInteraction(Form form) { |
|
45 super(form); |
|
46 } |
|
47 |
|
48 final void handleShowCurrentEvent() { |
|
49 super.handleShowCurrentEvent(); |
|
50 eswtApplyCurrentFocus(); |
|
51 } |
|
52 |
|
53 final void handleHideCurrentEvent() { |
|
54 super.handleHideCurrentEvent(); |
|
55 direction = NO_DIRECTION; |
|
56 } |
|
57 |
|
58 /* (non-Javadoc) |
|
59 * @see DefaultFormLayoutPolicy#eswtLayoutForm(int) |
|
60 */ |
|
61 final void eswtLayoutForm(int startIndex) { |
|
62 super.eswtLayoutForm(startIndex); |
|
63 |
|
64 // clear invalid selected item |
|
65 eswtCheckCurrentSelectedItem(); |
|
66 |
|
67 if (currentSelectedItem != null |
|
68 && (currentSelectedItem.isFocusable())) { |
|
69 eswtApplyCurrentFocus(); |
|
70 } |
|
71 else { |
|
72 // If there's no item currently selected try to find first |
|
73 // focusable item and set it current (if found): |
|
74 Item found = eswtGetNextFocusableItem( |
|
75 getItem(startIndex - 1), SWT.ARROW_RIGHT); |
|
76 if (found != null) { |
|
77 eswtSetCurrentSelectedItem(found, NO_DIRECTION); |
|
78 } |
|
79 else { |
|
80 eswtApplyCurrentFocus(); |
|
81 } |
|
82 } |
|
83 } |
|
84 |
|
85 /* (non-Javadoc) |
|
86 * @see DefaultFormLayoutPolicy#eswtSetCurrentItem(Item) |
|
87 */ |
|
88 boolean eswtSetCurrentItem(Item item) { |
|
89 boolean ret = super.eswtSetCurrentItem(item); |
|
90 if (ret && item != null && item.isFocusable()) { |
|
91 eswtSetCurrentSelectedItem(item, NO_DIRECTION); |
|
92 Logger.info("eswtSetCurrentItem" + item); |
|
93 } |
|
94 return ret; |
|
95 } |
|
96 |
|
97 /** |
|
98 * DefaultFormInteraction handler for key events.<br> |
|
99 * The DefaultFormInteraction is responsible to react to key presses |
|
100 * accordingly. The implementation moves focus and/or scrolls the form when |
|
101 * needed. The method is called by the Form. |
|
102 * |
|
103 * @param keyCode eSWT key code. |
|
104 * @param keyType eSWT key type. |
|
105 */ |
|
106 final void handleKeyEvent(int keyCode, int keyType) { |
|
107 Logger.method(this, "handleKeyEvent", currentSelectedItem, |
|
108 String.valueOf(keyCode), String.valueOf(keyType)); |
|
109 |
|
110 boolean isDirectionalKey = isDirectionKey(keyCode); |
|
111 if (keyType == SWT.KeyDown && isDirectionalKey) { |
|
112 eswtCheckCurrentSelectedItem(); |
|
113 } |
|
114 |
|
115 if (currentSelectedItem != null) { |
|
116 if (getLayouter(currentSelectedItem).eswtOfferKeyEvent( |
|
117 currentSelectedItem, keyCode, keyType)) { |
|
118 // if the key has been consumed |
|
119 return; |
|
120 } |
|
121 } |
|
122 |
|
123 // scrolling/focus traverse only happens on directional key's down event |
|
124 if (keyType == SWT.KeyDown && isDirectionalKey) { |
|
125 // try to find next focusable item |
|
126 Item next = eswtGetNextFocusableItem(currentSelectedItem, keyCode); |
|
127 |
|
128 // if no visible & focusable item was found to transfer focus |
|
129 if (next == currentSelectedItem) { |
|
130 // try to scroll a bit |
|
131 eswtSetScrollingPosition(getNextScrollingPosition(keyCode), |
|
132 true); |
|
133 // find next focusable after scrolling |
|
134 next = eswtGetNextFocusableItem(currentSelectedItem, keyCode); |
|
135 } |
|
136 |
|
137 if (next != currentSelectedItem) { |
|
138 //textfield always have to be fully visible when focused. |
|
139 if (next instanceof TextField) { |
|
140 eswtScrollToItem(next); |
|
141 } |
|
142 eswtSetCurrentSelectedItem(next, keyCode); |
|
143 } |
|
144 } |
|
145 } |
|
146 |
|
147 /** |
|
148 * Returns if the parameter is a eSWT directional key code. |
|
149 * |
|
150 * @param keyCode key code |
|
151 */ |
|
152 private boolean isDirectionKey(int keyCode) { |
|
153 return (keyCode == SWT.ARROW_DOWN || keyCode == SWT.ARROW_UP |
|
154 || keyCode == SWT.ARROW_LEFT || keyCode == SWT.ARROW_RIGHT); |
|
155 } |
|
156 |
|
157 /** |
|
158 * DefaultFormInteraction handler for pointer events.<br> |
|
159 * The method is called by the Form. |
|
160 * |
|
161 * @param x coordinate relative to scrolledComposite |
|
162 * @param y coordinate relative to scrolledComposite |
|
163 * @param type event type: SWT.MouseDown, SWT.MouseMove, SWT.MouseUp |
|
164 */ |
|
165 final void handlePointerEvent(int x, int y, int type) { |
|
166 Logger.method(this, "handlePointerEvent", String.valueOf(x), |
|
167 String.valueOf(y), String.valueOf(type)); |
|
168 |
|
169 // TODO: change when DirectUI style arrives. |
|
170 /* |
|
171 Item item; |
|
172 if (type == SWT.MouseMove) { |
|
173 if (currentlyUnderMouse == null |
|
174 || !currentlyUnderMouse.contains(x, y)) { |
|
175 if (currentlyUnderMouse != null) { |
|
176 //currentlyUnderMouse.getControl().setCapture(false); |
|
177 } |
|
178 item = eswtFindItemUnderMouse(x, y); |
|
179 if (item != null && item != currentSelectedItem |
|
180 && item.isFocusable()) { |
|
181 setCurrentItem(item); |
|
182 item.internalSetFocused(true); |
|
183 eswtSetCurrentSelectedItem(item); |
|
184 //following method causes all mouse events delivered to it |
|
185 |
|
186 currentlyUnderMouse.getControl().setCapture(true); |
|
187 Logger.warning("seting capture to:" + item); |
|
188 } |
|
189 } |
|
190 int currentVPosition = getVPosition(); |
|
191 boolean isMouseDirectionUp = false; |
|
192 boolean doScrolling = false; |
|
193 int localY = y; |
|
194 |
|
195 if (y <= currentVPosition) { |
|
196 localY = Math.max(0, y); |
|
197 eswtSetScrollingPosition(localY, true); |
|
198 isMouseDirectionUp = true; |
|
199 doScrolling = true; |
|
200 } |
|
201 else if (y > (currentVPosition + getFormHeight())) { |
|
202 //check for maxVPosition |
|
203 if (y > (eswtGetMaxVPosition() + getFormHeight())) { |
|
204 localY = eswtGetMaxVPosition() + getFormHeight(); |
|
205 } |
|
206 else { |
|
207 localY = y; |
|
208 } |
|
209 currentVPosition = localY - getFormHeight(); |
|
210 eswtSetScrollingPosition(currentVPosition, true); |
|
211 |
|
212 isMouseDirectionUp = false; |
|
213 doScrolling = true; |
|
214 } |
|
215 if (mousePressed && doScrolling) { |
|
216 resetEventTimer(isMouseDirectionUp, localY); |
|
217 } |
|
218 } |
|
219 else |
|
220 if (type == SWT.MouseDown) { |
|
221 mousePressed = true; |
|
222 item = eswtFindItemUnderMouse(x, y); |
|
223 if (item != null && item != currentSelectedItem |
|
224 && item.isFocusable() && getForm().getShell() == |
|
225 getForm().getShell().getDisplay().getActiveShell()) { |
|
226 //eswtScrollToItem(item); |
|
227 //following method causes all mouse events delivered to it |
|
228 |
|
229 //currentlyUnderMouse.getControl().setCapture(true); |
|
230 } |
|
231 } |
|
232 else if (type == SWT.MouseUp) { |
|
233 mousePressed = false; |
|
234 if (currentlyUnderMouse != null) { |
|
235 //currentlyUnderMouse.getControl().setCapture(false); |
|
236 } |
|
237 }*/ |
|
238 } |
|
239 |
|
240 /** |
|
241 * Find item at the specified point. |
|
242 * |
|
243 * @param x coordinate. |
|
244 * @param y coordinate. |
|
245 * @return Item. |
|
246 */ |
|
247 Item eswtFindItemUnderMouse(int x, int y) { |
|
248 Row itemRow; |
|
249 for (int i = 0; i < getRowCount(); i++) { |
|
250 itemRow = getRow(i); |
|
251 if (itemRow.getYShift() <= y && y <= itemRow.getBottomPosition()) { |
|
252 LayoutObject lo; |
|
253 for (int j = 0; j < itemRow.size(); j++) { |
|
254 lo = itemRow.getLayoutObject(j); |
|
255 if (lo.contains(x, y)) { |
|
256 Logger.info("Item under mouse: " |
|
257 + lo.getOwningItem()); |
|
258 currentlyUnderMouse = lo; |
|
259 return lo.getOwningItem(); |
|
260 } |
|
261 } |
|
262 break; |
|
263 } |
|
264 } |
|
265 return null; |
|
266 } |
|
267 |
|
268 /* (non-Javadoc) |
|
269 * @see DefaultFormLayoutPolicy#eswtHandleVisibilityChanges() |
|
270 */ |
|
271 protected void eswtHandleVisibilityChanges() { |
|
272 super.eswtHandleVisibilityChanges(); |
|
273 eswtCheckCurrentSelectedItem(); |
|
274 } |
|
275 |
|
276 /** |
|
277 * Gets next (or nearest) focusable item. |
|
278 * |
|
279 * @param fromItem Item where to start to search the next focusable item. |
|
280 * @param dir Search direction, one of the arrow key constants defined |
|
281 * in class SWT. |
|
282 * |
|
283 * @return Nearest focusable item or null if no item found. |
|
284 */ |
|
285 final Item eswtGetNextFocusableItem(Item fromItem, int dir) { |
|
286 Item nextItem = fromItem; |
|
287 |
|
288 switch (dir) { |
|
289 case SWT.ARROW_RIGHT: { |
|
290 LayoutObject obj = getLastLayoutObjectOfItem(fromItem); |
|
291 while ((obj = getNextLayoutObjectOfItem(obj, null)) != null) { |
|
292 Item owner = obj.getOwningItem(); |
|
293 if (owner != null && owner != fromItem |
|
294 && owner.isFocusable() |
|
295 && isPartiallyVisible(obj, Config.DFI_VISIBILITY_PERCENT)) { |
|
296 nextItem = owner; |
|
297 break; |
|
298 } |
|
299 } |
|
300 break; |
|
301 } |
|
302 |
|
303 case SWT.ARROW_LEFT: { |
|
304 LayoutObject obj = getFirstLayoutObjectOfItem(fromItem); |
|
305 while ((obj = getPrevLayoutObjectOfItem(obj, null)) != null) { |
|
306 Item owner = obj.getOwningItem(); |
|
307 if (owner != null && owner != fromItem |
|
308 && owner.isFocusable() |
|
309 && isPartiallyVisible(obj, Config.DFI_VISIBILITY_PERCENT)) { |
|
310 nextItem = owner; |
|
311 break; |
|
312 } |
|
313 } |
|
314 break; |
|
315 } |
|
316 |
|
317 case SWT.ARROW_DOWN: { |
|
318 int minDist = Integer.MAX_VALUE; |
|
319 LayoutObject start = getLastLayoutObjectOfItem(fromItem); |
|
320 LayoutObject obj = start; |
|
321 while ((obj = getNextLayoutObjectOfItem(obj, null)) != null) { |
|
322 Item owner = obj.getOwningItem(); |
|
323 if (owner != null && owner != fromItem |
|
324 && owner.isFocusable() && obj.isBelow(start) |
|
325 && isPartiallyVisible(obj, Config.DFI_VISIBILITY_PERCENT)) { |
|
326 int dist = obj.distanceTo(start); |
|
327 if (dist < minDist) { |
|
328 minDist = dist; |
|
329 nextItem = owner; |
|
330 } |
|
331 } |
|
332 } |
|
333 break; |
|
334 } |
|
335 |
|
336 case SWT.ARROW_UP: { |
|
337 int minDist = Integer.MAX_VALUE; |
|
338 LayoutObject start = getFirstLayoutObjectOfItem(fromItem); |
|
339 LayoutObject obj = start; |
|
340 while ((obj = getPrevLayoutObjectOfItem(obj, null)) != null) { |
|
341 Item owner = obj.getOwningItem(); |
|
342 if (owner != null && owner != fromItem |
|
343 && owner.isFocusable() && obj.isAbove(start) |
|
344 && isPartiallyVisible(obj, Config.DFI_VISIBILITY_PERCENT)) { |
|
345 int dist = obj.distanceTo(start); |
|
346 if (dist < minDist) { |
|
347 minDist = dist; |
|
348 nextItem = owner; |
|
349 } |
|
350 } |
|
351 } |
|
352 break; |
|
353 } |
|
354 |
|
355 default: |
|
356 } |
|
357 |
|
358 return nextItem; |
|
359 } |
|
360 |
|
361 /** |
|
362 * Check if the currentSelectedItem is valid and visible. If not then it |
|
363 * sets it to null. |
|
364 */ |
|
365 final void eswtCheckCurrentSelectedItem() { |
|
366 if (currentSelectedItem != null) { |
|
367 if (currentSelectedItem.getParent() != getForm() |
|
368 || !currentSelectedItem.isVisible()) { |
|
369 // we need to find another |
|
370 Logger.method(this, "eswtCheckCurrentSelectedItem"); |
|
371 eswtSetCurrentSelectedItem(null, NO_DIRECTION); |
|
372 } |
|
373 } |
|
374 } |
|
375 |
|
376 /** |
|
377 * Sets currentSelectedItem and sets focus to it.<br> |
|
378 * If one of form's items is already selected when this method is called, |
|
379 * removes focus from old item and then moves focus to new one. |
|
380 * |
|
381 * @param item Item to set as current selected. If null, nothing happens. |
|
382 * @param dir Direction which is delivered to layouter. |
|
383 */ |
|
384 void eswtSetCurrentSelectedItem(Item item, int dir) { |
|
385 if (currentSelectedItem != item) { |
|
386 Logger.info(this + "::SelectedItem: " |
|
387 + currentSelectedItem + " --(" + dir + ")--> " + item); |
|
388 |
|
389 // Save direction |
|
390 direction = dir; |
|
391 // Remove focus from currentSelectedItem and notify its Layouter. |
|
392 if (currentSelectedItem != null) { |
|
393 getLayouter(currentSelectedItem).eswtFocusLost( |
|
394 currentSelectedItem); |
|
395 } |
|
396 |
|
397 // Set new currentSelectedItem, must be focusable or null |
|
398 currentSelectedItem = item; |
|
399 |
|
400 // Set focus to currentSelectedItem and notify its Layouter. |
|
401 if (currentSelectedItem != null) { |
|
402 getLayouter(currentSelectedItem).eswtFocusGained( |
|
403 currentSelectedItem, dir); |
|
404 } |
|
405 |
|
406 // Apply eSWT focus to currentSelectedItem's control |
|
407 eswtApplyCurrentFocus(); |
|
408 } |
|
409 } |
|
410 |
|
411 /** |
|
412 * Sets currentSelectedItem and sets focus to it.<br> |
|
413 * If one of form's items is already selected when this method is called, |
|
414 * removes focus from old item and then moves focus to new one. |
|
415 * |
|
416 * @param item Item to set as current selected. If null, nothing happens. |
|
417 * @param dir Direction which is delivered to layouter. |
|
418 */ |
|
419 void eswtSetCurrentSelectedItem(Item item) { |
|
420 if (currentSelectedItem != item) { |
|
421 Logger.info(this + "::SelectedItem: " |
|
422 + currentSelectedItem + " ---> " + item); |
|
423 |
|
424 // Remove focus from currentSelectedItem and notify its Layouter. |
|
425 if (currentSelectedItem != null) { |
|
426 getLayouter(currentSelectedItem).eswtFocusLost( |
|
427 currentSelectedItem); |
|
428 } |
|
429 |
|
430 // Set new currentSelectedItem, must be focusable or null |
|
431 currentSelectedItem = item; |
|
432 |
|
433 // Set focus to currentSelectedItem and notify its Layouter. |
|
434 if (currentSelectedItem != null) { |
|
435 getLayouter(currentSelectedItem).eswtFocusGained( |
|
436 currentSelectedItem, NO_DIRECTION); |
|
437 } |
|
438 |
|
439 // Apply eSWT focus to currentSelectedItem's control |
|
440 //eswtApplyCurrentFocus(); |
|
441 } |
|
442 } |
|
443 |
|
444 /** |
|
445 * Sets focus to currentSelectedItem's control if its partially visible. |
|
446 * Otherwise it sets dummy focus to form's composite.<br> |
|
447 * <br> |
|
448 * Note that this method applies focus only to eSWT control. Item focus |
|
449 * update and layouter notifications are handled in method |
|
450 * <code>eswtSetCurrentSelectedItem()</code>.<br> |
|
451 * If currentSelectedItem is null or form is not shown, this method has no |
|
452 * effect. |
|
453 */ |
|
454 void eswtApplyCurrentFocus() { |
|
455 if (isFormCurrent()) { |
|
456 // if any of the Item's LayoutObjects is visible |
|
457 if (isItemPartiallyVisible(currentSelectedItem)) { |
|
458 Logger.method(this, "ApplyFocus", currentSelectedItem); |
|
459 eswtSetFocusToFirstControl(currentSelectedItem); |
|
460 } |
|
461 else { |
|
462 Logger.method(this, "ApplyFocus", "dummy"); |
|
463 formComposite.forceFocus(); |
|
464 } |
|
465 } |
|
466 } |
|
467 |
|
468 /** |
|
469 * If the Item is valid and it is layouted, then sets the Item's first |
|
470 * LayoutObject focused. |
|
471 * |
|
472 * @param item an item which first LayoutObject is set focused. |
|
473 */ |
|
474 void eswtSetFocusToFirstControl(Item item) { |
|
475 if (item != null && item.isFocusable()) { |
|
476 LayoutObject lo = getFirstLayoutObjectOfItem(item); |
|
477 if (lo != null) { |
|
478 lo.getControl().forceFocus(); |
|
479 } |
|
480 } |
|
481 } |
|
482 |
|
483 /** |
|
484 * Gets Current selected item. |
|
485 * |
|
486 * @return Current selected item. May also return null. |
|
487 */ |
|
488 Item getCurrentSelectedItem() { |
|
489 return currentSelectedItem; |
|
490 } |
|
491 |
|
492 /** |
|
493 * Get the direction of scrolling. |
|
494 * |
|
495 * @return direction of scrolling. |
|
496 */ |
|
497 int getDirection() { |
|
498 return direction; |
|
499 } |
|
500 |
|
501 /* (non-Javadoc) |
|
502 * @see DefaultFormLayoutPolicy#eswtResizeItemAndShift(Item) |
|
503 */ |
|
504 int eswtResizeItemAndShift(Item item) { |
|
505 // save the state of the last row before resizing and Shifting. |
|
506 boolean itemWasVisible = isItemPartiallyVisible(item); |
|
507 |
|
508 int newVPosition = super.eswtResizeItemAndShift(item); |
|
509 |
|
510 if (item == currentSelectedItem) { |
|
511 if (itemWasVisible) { |
|
512 int itemTop = getItemTopPosition(item); |
|
513 int itemBottom = getItemBottomPosition(item); |
|
514 // currentSelectedItem has to be focused if it was focused |
|
515 // before resizing e.g TextField when it is resized by adding a |
|
516 // new row and it was in the bottom of the Screen. |
|
517 if (newVPosition <= itemTop |
|
518 && (newVPosition + getFormHeight()) >= itemBottom) { |
|
519 // do not change vPosition; |
|
520 } |
|
521 else if (newVPosition > itemTop) { |
|
522 newVPosition = itemTop; |
|
523 } |
|
524 else if ((newVPosition + getFormHeight()) < itemBottom) { |
|
525 newVPosition = itemBottom - getFormHeight(); |
|
526 } |
|
527 } |
|
528 } |
|
529 return newVPosition; |
|
530 } |
|
531 |
|
532 private Timer eventTimer = new Timer(); |
|
533 private EventGeneratorTask eventTask; |
|
534 |
|
535 /** |
|
536 * Reset timer for do layout with a given start index. |
|
537 */ |
|
538 private void resetEventTimer(boolean directionUp, int y) { |
|
539 if (eventTimer != null) { |
|
540 if (eventTask != null) { |
|
541 eventTask.cancel(); |
|
542 eventTask = null; |
|
543 } |
|
544 // schedule new timer |
|
545 eventTask = new EventGeneratorTask(directionUp, y); |
|
546 eventTimer.schedule(eventTask, Config.DFI_EVENT_TIMER_DELAY); |
|
547 } |
|
548 } |
|
549 |
|
550 /** |
|
551 * Form Timer task. Triggers the formComposite to Layout. |
|
552 */ |
|
553 class EventGeneratorTask extends TimerTask { |
|
554 |
|
555 private boolean isUpDirection; |
|
556 private int localY; |
|
557 |
|
558 public EventGeneratorTask(boolean direction, int y) { |
|
559 isUpDirection = direction; |
|
560 localY = y; |
|
561 Logger.info("y is " + localY); |
|
562 } |
|
563 |
|
564 public void run() { |
|
565 if (isUpDirection) { |
|
566 localY -= Config.DFI_EVENT_MOVE_DELTA; |
|
567 } |
|
568 else { |
|
569 localY += Config.DFI_EVENT_MOVE_DELTA; |
|
570 } |
|
571 handlePointerEvent(0, localY, SWT.MouseMove); |
|
572 } |
|
573 } |
|
574 |
|
575 } |