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