001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package javax.microedition.lcdui;
028:
029: import com.sun.midp.lcdui.EventConstants;
030:
031: import com.sun.midp.lcdui.*;
032: import com.sun.midp.configurator.Constants;
033: import com.sun.midp.chameleon.skins.DateEditorSkin;
034: import com.sun.midp.chameleon.skins.ChoiceGroupSkin;
035: import com.sun.midp.chameleon.layers.ScrollIndLayer;
036: import com.sun.midp.chameleon.layers.ScrollablePopupLayer;
037: import com.sun.midp.chameleon.skins.ScrollIndSkin;
038: import com.sun.midp.chameleon.skins.resources.ScrollIndResourcesConstants;
039:
040: /**
041: * This is a popup layer that handles a sub-popup within the date editor,
042: * which is also a popup layer.
043: */
044: class DEPopupLayer extends ScrollablePopupLayer {
045: /**
046: * Constructs a date editor sub-popup layer, which behaves like a
047: * popup-choicegroup, given a string array of elements that constitute
048: * the available list of choices to select from.
049: *
050: * @param editor The DateEditor that triggered this popup layer.
051: * @param elements String array holding the list of choices.
052: * @param selectedIndex the index to place the initial highlight on.
053: * @param circularTraversal true if traversal past the last item should
054: * jump to the beginning
055: */
056: DEPopupLayer(DateEditor editor, String[] elements,
057: int selectedIndex, boolean circularTraversal) {
058: super ((Image) null, DateEditorSkin.COLOR_POPUPS_BG);
059: this .editor = editor;
060:
061: setContent(elements, selectedIndex);
062: this .circularTraversal = circularTraversal;
063: }
064:
065: /**
066: * Populates this sub-popup layer with new elements.
067: * The number of elements before and after should be the same
068: * if the popup already existed.
069: *
070: * @param newElements String array holding the list of choices.
071: * @param selectedIndex the index to place the initial highlight on.
072: */
073: protected void setContent(String[] newElements, int selectedIndex) {
074: if (newElements != null) {
075: numElements = newElements.length;
076: elements = new String[numElements];
077: System.arraycopy(newElements, 0, elements, 0, numElements);
078:
079: this .selectedIndex = selectedIndex;
080: hilightedIndex = selectedIndex;
081: }
082: startIndex = 0;
083: }
084:
085: /**
086: * Initializes the popup layer.
087: */
088: protected void initialize() {
089: super .initialize();
090: viewport = new int[4];
091: }
092:
093: /**
094: * Sets the bounds of the popup layer.
095: *
096: * @param x the x-coordinate of the popup layer location
097: * @param y the y-coordinate of the popup layer location
098: * @param w the width of this popup layer in open state
099: * @param h the height of this popup layer in open state
100: */
101: public void setBounds(int x, int y, int w, int h) {
102: super .setBounds(x, y, w, h);
103: transparent = false;
104:
105: // set viewport in popup's coordinate system
106: viewport[X] = 2;
107: viewport[Y] = 0;
108: viewport[W] = bounds[W] - 3;
109: viewport[H] = bounds[H] - 3;
110:
111: elementsToFit = viewport[H] / elementHeight;
112: if (elementsToFit < numElements) {
113: sbVisible = true;
114: } else {
115: elementsToFit = numElements;
116: sbVisible = false;
117: }
118: }
119:
120: /**
121: * Helper function to determine the itemIndex at the x,y position
122: *
123: * @param x,y pointer coordinates
124: * @return item's index since 0, or PRESS_OUT_OF_BOUNDS.
125: *
126: */
127: private int itemIndexAtPointerPosition(int x, int y) {
128: int id = PRESS_OUT_OF_BOUNDS;
129: if (containsPoint(x + bounds[X], y + bounds[Y])) {
130: id = (int) (y / elementHeight);
131: }
132: return id;
133: }
134:
135: /**
136: * Handles pointer event in the open popup.
137: *
138: * @param type - The type of this pointer event (pressed, released, dragged)
139: * @param x x coordinate
140: * @param x y coordinate
141: * @return true always, since popupLayers swallow all pointer events
142: */
143: public boolean pointerInput(int type, int x, int y) {
144: boolean consume = true;
145: switch (type) {
146: case EventConstants.PRESSED:
147: itemIndexWhenPressed = itemIndexAtPointerPosition(x, y);
148: if (itemIndexWhenPressed == PRESS_OUT_OF_BOUNDS) {
149: hide();
150: consume = false;
151: } else if (itemIndexWhenPressed >= 0 &&
152: // press on valid item
153: hilightedIndex != itemIndexWhenPressed + startIndex) {
154: hilightedIndex = itemIndexWhenPressed + startIndex;
155: requestRepaint();
156: }
157: break;
158: case EventConstants.RELEASED:
159: int itemIndexWhenReleased = itemIndexAtPointerPosition(x, y);
160:
161: if (itemIndexWhenReleased == itemIndexWhenPressed) {
162: if (itemIndexWhenPressed >= 0) {
163: keyInput(EventConstants.PRESSED,
164: Constants.KEYCODE_SELECT);
165: } else {
166: hide();
167: }
168: }
169:
170: if (itemIndexWhenReleased == PRESS_OUT_OF_BOUNDS) {
171: consume = false;
172: }
173: //remember to reset the variables
174: itemIndexWhenPressed = PRESS_OUT_OF_BOUNDS;
175: break;
176: }
177: return consume;
178: }
179:
180: /**
181: * Handles key event in the open popup.
182: *
183: * @param type - The type of this key event (pressed, released)
184: * @param code - The code of this key event
185: * @return true always, since popupLayers swallow all key events
186: */
187: public boolean keyInput(int type, int code) {
188: if ((type == EventConstants.PRESSED || type == EventConstants.REPEATED)
189: && editor != null) {
190: switch (code) {
191: case Constants.KEYCODE_SELECT:
192: editor.keyInput(type, code);
193: break;
194: case Constants.KEYCODE_UP:
195: case Constants.KEYCODE_DOWN:
196: case Constants.KEYCODE_LEFT:
197: case Constants.KEYCODE_RIGHT:
198: traverseInPopup(code);
199: break;
200: }
201: }
202: // PopupLayers always swallow all key events
203: return true;
204: }
205:
206: /**
207: * Paints popup background (including borders) and scrollbar
208: * if it is present.
209: * @param g - The graphics object to paint background on
210: */
211: public void paintBackground(Graphics g) {
212: super .paintBackground(g);
213: g.setColor(DateEditorSkin.COLOR_BORDER);
214: g.drawRect(0, -1, bounds[W] - 1, bounds[H]);
215:
216: if (sbVisible
217: && ScrollIndSkin.MODE == ScrollIndResourcesConstants.MODE_ARROWS) {
218: int sbX = bounds[W] - 6;
219: int sbY = 5;
220: int sbH = bounds[H] - 12;
221: int thumbY = sbY
222: - 4
223: + ((((hilightedIndex + 1) * 100) / numElements) * sbH)
224: / 100;
225: g.setColor(DateEditorSkin.COLOR_BORDER);
226:
227: // draw scrollbar
228: g.drawLine(sbX, sbY, sbX, sbY + sbH - 1);
229:
230: // draw scrollbar thumb
231: g.fillRect(sbX - (3 / 2), thumbY, 3, 4);
232: }
233: }
234:
235: /**
236: * Paints the body of the popup layer.
237: *
238: * @param g The graphics context to paint to
239: */
240: public void paintBody(Graphics g) {
241: boolean hilighted = false;
242: int translatedY = 0;
243:
244: int transY = elementHeight;
245: g.translate(2, 0);
246:
247: endIndex = startIndex + (elementsToFit - 1);
248:
249: if (hilightedIndex > endIndex) {
250: endIndex = hilightedIndex;
251: startIndex = endIndex - (elementsToFit - 1);
252: }
253:
254: g.setFont(DateEditorSkin.FONT_POPUPS);
255: for (int i = startIndex; i <= endIndex; i++) {
256: hilighted = (i == hilightedIndex);
257:
258: if (hilighted) {
259: g.setColor(DateEditorSkin.COLOR_TRAVERSE_IND);
260: g.fillRect(0, 0, elementWidth - 7, elementHeight);
261: }
262:
263: g.setColor(0);
264: g.drawString(elements[i], 2, 0, 0);
265: g.translate(0, transY);
266: translatedY += transY;
267: }
268:
269: g.translate(-2, -translatedY);
270: }
271:
272: // ********** package private *********** //
273:
274: /**
275: * Gets currently selected index.
276: *
277: * @return currently selected index
278: */
279: int getSelectedIndex() {
280: return hilightedIndex;
281: }
282:
283: /**
284: * Sets currently selected index.
285: *
286: * @param selId currently selected index
287: */
288: void setSelectedIndex(int selId) {
289: selectedIndex = selId;
290: }
291:
292: /**
293: * Set the choice element size (width and height).
294: *
295: * @param w width of the element
296: * @param h height of the element
297: */
298: void setElementSize(int w, int h) {
299: elementWidth = w;
300: elementHeight = h;
301: }
302:
303: /**
304: * Handle traversal in the open popup.
305: *
306: * @param code the code of the key event
307: * @return true always, since popupLayers swallow all key events
308: */
309: boolean traverseInPopup(int code) {
310: boolean updated = true;
311: if (code == Constants.KEYCODE_UP) {
312: if (hilightedIndex > 0) {
313: hilightedIndex--;
314: if (hilightedIndex < startIndex) {
315: startIndex--;
316: }
317: } else if (circularTraversal) {
318: // jump to the last element
319: hilightedIndex = numElements - 1;
320: startIndex = hilightedIndex - elementsToFit + 1;
321: } else {
322: updated = false;
323: }
324: } else if (code == Constants.KEYCODE_DOWN) {
325: if (hilightedIndex < (numElements - 1)) {
326: hilightedIndex++;
327: if (hilightedIndex > endIndex) {
328: startIndex++;
329: }
330: } else if (circularTraversal) {
331: // jump to the first element
332: hilightedIndex = 0;
333: startIndex = 0;
334: } else {
335: updated = false;
336: }
337: }
338: if (updated) {
339: updateScrollIndicator();
340: requestRepaint();
341: }
342:
343: return true;
344: }
345:
346: /**
347: * show current popup
348: * @param sLF popup owner screen
349: */
350: public void show(ScreenLFImpl sLF) {
351: this .sLF = sLF;
352: sLF.lGetCurrentDisplay().showPopup(this );
353: this .open = true;
354:
355: // update startIndex to let the selected item shown
356: hilightedIndex = selectedIndex;
357: startIndex = hilightedIndex;
358: if (startIndex > numElements - elementsToFit) {
359: // startIndex too bottom, adjust it
360: startIndex = numElements - elementsToFit;
361: }
362: if (startIndex < 0) {
363: startIndex = 0;
364: }
365:
366: if (ScrollIndSkin.MODE == ScrollIndResourcesConstants.MODE_BAR) {
367: setScrollInd(ScrollIndLayer.getInstance(ScrollIndSkin.MODE));
368: }
369:
370: updateScrollIndicator();
371: }
372:
373: /**
374: * hide current popup
375: */
376: public void hide() {
377: if (scrollInd != null) {
378: scrollInd.setVisible(false);
379: sbVisible = false;
380: updateScrollIndicator();
381: setScrollInd(null);
382: }
383: if (this .sLF != null) {
384: sLF.lGetCurrentDisplay().hidePopup(this );
385: }
386: editor.requestRepaint();
387: // it is necessary, to make sure correctly showing the space occupied by this popup
388: this .sLF = null;
389: this .open = false;
390: }
391:
392: /**
393: * Scroll content inside of the DEPopup.
394: * @param scrollType scrollType. Scroll type can be one of the following
395: * @see ScrollIndLayer.SCROLL_NONE
396: * @see ScrollIndLayer.SCROLL_PAGEUP
397: * @see ScrollIndLayer.SCROLL_PAGEDOWN
398: * @see ScrollIndLayer.SCROLL_LINEUP
399: * @see ScrollIndLayer.SCROLL_LINEDOWN or
400: * @see ScrollIndLayer.SCROLL_THUMBTRACK
401: * @param thumbPosition
402: */
403: public void scrollContent(int scrollType, int thumbPosition) {
404: switch (scrollType) {
405: case ScrollIndLayer.SCROLL_PAGEUP:
406: uScrollViewport(Canvas.UP);
407: break;
408: case ScrollIndLayer.SCROLL_PAGEDOWN:
409: uScrollViewport(Canvas.DOWN);
410: break;
411: case ScrollIndLayer.SCROLL_LINEUP:
412: uScrollByLine(Canvas.UP);
413: break;
414: case ScrollIndLayer.SCROLL_LINEDOWN:
415: uScrollByLine(Canvas.DOWN);
416: break;
417: case ScrollIndLayer.SCROLL_THUMBTRACK:
418: uScrollAt(thumbPosition);
419: break;
420: default:
421: break;
422: }
423: }
424:
425: /**
426: * Perform a page flip in the given direction. This method will
427: * attempt to scroll the view to show as much of the next page
428: * as possible. It uses the locations and bounds of the items on
429: * the page to best determine a new location - taking into account
430: * items which may lie on page boundaries as well as items which
431: * may span several pages.
432: *
433: * @param dir the direction of the flip, either DOWN or UP
434: */
435: private void uScrollViewport(int dir) {
436: switch (dir) {
437: case Canvas.UP:
438: startIndex -= elementsToFit - 1; // with top item still visible in new viewport
439: if (startIndex < 0) {
440: startIndex = 0;
441: }
442: break;
443: case Canvas.DOWN:
444: startIndex += elementsToFit - 1; // with bottom item still visible in new viewport
445: if (startIndex > numElements - elementsToFit) {
446: // startIndex too bottom, adjust it
447: startIndex = numElements - elementsToFit;
448: }
449: break;
450: }
451: updatePopupLayer();
452: }
453:
454: /**
455: * Perform a line scrolling in the given direction. This method will
456: * attempt to scroll the view to show next/previous line.
457: *
458: * @param dir the direction of the flip, either DOWN or UP
459: */
460: private void uScrollByLine(int dir) {
461: switch (dir) {
462: case Canvas.UP:
463: startIndex--;
464: if (startIndex < 0) {
465: startIndex = 0;
466: }
467: break;
468: case Canvas.DOWN:
469: startIndex++;
470: if (startIndex > numElements - elementsToFit) {
471: startIndex = numElements - elementsToFit;
472: }
473: break;
474: }
475: updatePopupLayer();
476: }
477:
478: /**
479: * Perform a scrolling at the given position.
480: * @param context position
481: */
482: private void uScrollAt(int position) {
483: startIndex = (numElements - elementsToFit) * position / 100;
484: if (startIndex < 0) {
485: startIndex = 0;
486: } else if (startIndex > numElements - elementsToFit) {
487: startIndex = numElements - elementsToFit;
488: }
489: updatePopupLayer();
490: }
491:
492: /**
493: * Updates the scroll indicator.
494: */
495: public void updateScrollIndicator() {
496: if (scrollInd != null) {
497: scrollInd.update(null);
498: if (sbVisible) {
499: scrollInd.setVerticalScroll(startIndex * 100
500: / (numElements - elementsToFit), elementsToFit
501: * 100 / numElements);
502: } else {
503: scrollInd.setVerticalScroll(0, 100);
504: }
505: super .updateScrollIndicator();
506: }
507: }
508:
509: /**
510: * This method initiate repaint of the popup layer
511: *
512: */
513: private void updatePopupLayer() {
514: // correct hilighted index depending on new viewport. The hilighted item
515: // always has to be visible
516: if (hilightedIndex < startIndex) {
517: hilightedIndex = startIndex;
518: } else if (hilightedIndex >= startIndex + elementsToFit) {
519: hilightedIndex = startIndex + elementsToFit - 1;
520: }
521: updateScrollIndicator();
522: addDirtyRegion();
523: requestRepaint();
524: }
525:
526: // ********* attributes ********* //
527:
528: /**
529: * The DateEditor that triggered this popup layer.
530: */
531: DateEditor editor;
532:
533: /**
534: * The viewport setting inside this popup (X, Y, W, H).
535: * It is set in this layer's coordinate system.
536: */
537: private int viewport[];
538:
539: /**
540: * Indicates if this popup layer is shown (true) or hidden (false).
541: */
542: boolean open;
543:
544: /**
545: * Number of elements (list of choices) that constitute this
546: * popup layer.
547: */
548: private int numElements;
549:
550: /**
551: * The list of choices in this popup layer.
552: */
553: private String[] elements;
554:
555: /**
556: * The width of an element.
557: */
558: private int elementWidth;
559:
560: /**
561: * The height of an element.
562: */
563: private int elementHeight;
564:
565: /**
566: * Number of elements that can be shown within the viewport.
567: */
568: private int elementsToFit;
569:
570: /**
571: * Indicates whether we do/do not need to draw a scrollbar in
572: * this popup layer.
573: */
574: private boolean sbVisible; // = false;
575:
576: /**
577: * The start index of the chosen list of choices from the complete
578: * list, to be displayed within the viewport.
579: */
580: private int startIndex = 0;
581:
582: /**
583: * The end index of the chosen list of choices from the complete
584: * list, to be displayed within the viewport.
585: */
586: private int endIndex = 0;
587:
588: /**
589: * The index that is currently highlighted, is taken as the selected
590: * index when popup closes.
591: */
592: private int hilightedIndex;
593:
594: /** Selected index. Index accepted by pressing set or fire key */
595: private int selectedIndex;
596:
597: /**
598: * True if traversal past the last item in the popup should jump to the
599: * beginning and false if attempts to traverse past the last or the first
600: * items will have no effect.
601: */
602: private boolean circularTraversal; // = false
603:
604: /* screen impl which owns the dateEditor and this DEPopupLayer */
605: private ScreenLFImpl sLF;
606:
607: /* pointer pressed outside of the Layer's bounds */
608: final static int PRESS_OUT_OF_BOUNDS = -1;
609:
610: /* variable used in pointerInput handling */
611: private int itemIndexWhenPressed = PRESS_OUT_OF_BOUNDS;
612:
613: }
|