001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.awt;
019:
020: import java.awt.event.FocusEvent;
021: import java.awt.event.ItemEvent;
022: import java.awt.event.ItemListener;
023: import java.awt.event.KeyEvent;
024: import java.awt.event.MouseEvent;
025: import java.awt.event.MouseWheelEvent;
026: import java.awt.event.MouseWheelListener;
027: import java.awt.font.FontRenderContext;
028: import java.util.ArrayList;
029: import java.util.EventListener;
030: import java.util.Iterator;
031:
032: import javax.accessibility.Accessible;
033: import javax.accessibility.AccessibleAction;
034: import javax.accessibility.AccessibleContext;
035: import javax.accessibility.AccessibleRole;
036:
037: import org.apache.harmony.awt.ButtonStateController;
038: import org.apache.harmony.awt.ChoiceStyle;
039: import org.apache.harmony.awt.internal.nls.Messages;
040: import org.apache.harmony.awt.state.ChoiceState;
041:
042: public class Choice extends Component implements ItemSelectable,
043: Accessible {
044: private static final long serialVersionUID = -4075310674757313071L;
045:
046: private static final int BORDER_SIZE = 2;
047:
048: static final Insets INSETS = new Insets(BORDER_SIZE, BORDER_SIZE,
049: BORDER_SIZE, BORDER_SIZE);
050:
051: final ChoiceStyle popupStyle;
052:
053: int selectedIndex = -1;
054:
055: private final AWTListenerList<ItemListener> itemListeners = new AWTListenerList<ItemListener>(
056: this );
057:
058: private final java.util.List<String> items = new ArrayList<String>();
059:
060: private final State state;
061:
062: private final ChoiceStateController stateController;
063:
064: protected class AccessibleAWTChoice extends
065: Component.AccessibleAWTComponent implements
066: AccessibleAction {
067: private static final long serialVersionUID = 7175603582428509322L;
068:
069: public AccessibleAWTChoice() {
070: // default constructor is public
071: }
072:
073: @Override
074: public AccessibleRole getAccessibleRole() {
075: return AccessibleRole.COMBO_BOX;
076: }
077:
078: @Override
079: public AccessibleAction getAccessibleAction() {
080: return this ;
081: }
082:
083: public int getAccessibleActionCount() {
084: return 0;
085: }
086:
087: public boolean doAccessibleAction(int i) {
088: return false;
089: }
090:
091: public String getAccessibleActionDescription(int i) {
092: return null;
093: }
094:
095: }
096:
097: class State extends Component.ComponentState implements ChoiceState {
098:
099: final Dimension textSize = new Dimension();
100:
101: public boolean isPressed() {
102: return stateController.isPressed();
103: }
104:
105: public String getText() {
106: return getSelectedItem();
107: }
108:
109: public Dimension getTextSize() {
110: return textSize;
111: }
112:
113: public void setTextSize(Dimension size) {
114: textSize.setSize(size);
115: }
116:
117: public Rectangle getButtonBounds() {
118: return new Rectangle(w - h + 2, 2, h - 4, h - 4);
119: }
120:
121: public Rectangle getTextBounds() {
122: return new Rectangle(4, 3, w - h - 5, h - 6);
123: }
124:
125: }
126:
127: class ChoiceStateController extends ButtonStateController implements
128: MouseWheelListener {
129:
130: /**
131: * popup window containing list of items
132: */
133: private final ChoicePopupBox popup;
134:
135: public ChoiceStateController() {
136: super (Choice.this );
137: popup = createPopup();
138: }
139:
140: protected ChoicePopupBox createPopup() {
141: return new ChoicePopupBox(Choice.this );
142: }
143:
144: @Override
145: public void focusLost(FocusEvent fe) {
146: super .focusLost(fe);
147: popup.hide();
148: }
149:
150: @Override
151: public void keyPressed(KeyEvent e) {
152: boolean alt = e.isAltDown();
153: int blockSize = ChoicePopupBox.PAGE_SIZE - 1;
154: int count = getItemCount();
155: switch (e.getKeyCode()) {
156: case KeyEvent.VK_UP:
157: if (alt) {
158: popup();
159: break;
160: }
161: case KeyEvent.VK_LEFT:
162: changeSelection(-1);
163: break;
164: case KeyEvent.VK_DOWN:
165: if (alt) {
166: popup();
167: break;
168: }
169: case KeyEvent.VK_RIGHT:
170: changeSelection(1);
171: break;
172: case KeyEvent.VK_PAGE_UP:
173: changeSelection(-blockSize);
174: break;
175: case KeyEvent.VK_PAGE_DOWN:
176: changeSelection(blockSize);
177: break;
178: case KeyEvent.VK_HOME:
179: changeSelection(-getSelectedIndex());
180: break;
181: case KeyEvent.VK_END:
182: changeSelection(count - getSelectedIndex());
183: break;
184: }
185: }
186:
187: /**
188: * Moves selection up(incr < 0) or down(incr > 0)
189: *
190: * @param incr distance from the current item in items
191: */
192: void changeSelection(int incr) {
193: int newSel = getValidIndex(getSelectedIndex() + incr);
194: if (popup.isVisible()) {
195: popup.changeSelection(incr);
196: newSel = popup.selectedItem;
197: }
198: selectAndFire(newSel);
199: }
200:
201: @Override
202: public void keyReleased(KeyEvent e) {
203: // don't call super here as in keyPressed
204: }
205:
206: /**
207: * Called on mouse release
208: */
209: @Override
210: protected void fireEvent() {
211: popup();
212: }
213:
214: /**
215: * Shows popup list if it's not visible, hides otherwise
216: */
217: private void popup() {
218: if (!popup.isVisible()) {
219: popup.show();
220: } else {
221: popup.hide();
222: }
223: }
224:
225: @Override
226: public void mousePressed(MouseEvent me) {
227: super .mousePressed(me);
228: // TODO: show/hide popup here
229: // if (me.getButton() != MouseEvent.BUTTON1) {
230: // return;
231: // }
232: // if (me.isPopupTrigger()) {
233: // popup();
234: // }
235: }
236:
237: public void mouseWheelMoved(MouseWheelEvent e) {
238: changeSelection(e.getUnitsToScroll());
239: }
240:
241: }
242:
243: /**
244: * Creates customized choice
245: *
246: * @param choiceStyle style of custom choice: defines custom list popup
247: * window location and size
248: */
249: Choice(ChoiceStyle choiceStyle) {
250: super ();
251: state = new State();
252: popupStyle = choiceStyle;
253: stateController = new ChoiceStateController();
254:
255: addAWTMouseListener(stateController);
256: addAWTKeyListener(stateController);
257: addAWTFocusListener(stateController);
258: addAWTMouseWheelListener(stateController);
259: }
260:
261: public Choice() throws HeadlessException {
262: this (new ChoiceStyle() {
263:
264: public int getPopupX(int x, int width, int choiceWidth,
265: int screenWidth) {
266: return x;
267: }
268:
269: public int getPopupWidth(int choiceWidth) {
270: return choiceWidth;
271: }
272:
273: });
274: toolkit.lockAWT();
275: try {
276:
277: } finally {
278: toolkit.unlockAWT();
279: }
280: }
281:
282: public void add(String item) {
283: toolkit.lockAWT();
284: try {
285: if (item == null) {
286: // awt.103=item is null
287: throw new NullPointerException(Messages
288: .getString("awt.103")); //$NON-NLS-1$
289: }
290: if (items.size() == 0) {
291: selectedIndex = 0;
292: }
293: items.add(items.size(), item);
294:
295: } finally {
296: toolkit.unlockAWT();
297: }
298: }
299:
300: public void remove(String item) {
301: toolkit.lockAWT();
302: try {
303: int index = items.indexOf(item);
304: if (index == -1) {
305: // awt.104=item doesn't exist in the choice menu
306: throw new IllegalArgumentException(Messages
307: .getString("awt.104")); //$NON-NLS-1$
308: }
309: if (selectedIndex == index) {
310: selectedIndex = 0;
311: }
312: items.remove(item);
313: if (selectedIndex > index) {
314: selectedIndex--;
315: }
316:
317: } finally {
318: toolkit.unlockAWT();
319: }
320: }
321:
322: public void remove(int position) {
323: toolkit.lockAWT();
324: try {
325: if (selectedIndex == position) {
326: selectedIndex = 0;
327: }
328: items.remove(position);
329:
330: if (selectedIndex > position) {
331: selectedIndex--;
332: }
333: if (items.size() == 0) {
334: selectedIndex = -1;
335: }
336: } finally {
337: toolkit.unlockAWT();
338: }
339: }
340:
341: public void removeAll() {
342: toolkit.lockAWT();
343: try {
344: items.clear();
345: selectedIndex = -1;
346: } finally {
347: toolkit.unlockAWT();
348: }
349: }
350:
351: public void insert(String item, int index) {
352: if (index < 0) {
353: // awt.105=index less than zero
354: throw new IllegalArgumentException(Messages
355: .getString("awt.105")); //$NON-NLS-1$
356: }
357:
358: if (item == null) {
359: // awt.105='item' parameter is null
360: throw new NullPointerException(Messages.getString(
361: "awt.01", "item")); //$NON-NLS-1$ //$NON-NLS-2$
362: }
363:
364: toolkit.lockAWT();
365: try {
366: int idx = Math.min(items.size(), index);
367: if (items.size() == 0) {
368: selectedIndex = 0;
369: }
370: items.add(idx, item);
371: if (idx <= selectedIndex) {
372: selectedIndex = 0;
373: }
374: } finally {
375: toolkit.unlockAWT();
376: }
377: }
378:
379: @Override
380: public void addNotify() {
381: toolkit.lockAWT();
382: try {
383: super .addNotify();
384: setSize(getMinimumSize());
385: } finally {
386: toolkit.unlockAWT();
387: }
388: }
389:
390: @Override
391: public AccessibleContext getAccessibleContext() {
392: toolkit.lockAWT();
393: try {
394: return super .getAccessibleContext();
395: } finally {
396: toolkit.unlockAWT();
397: }
398: }
399:
400: @Override
401: protected String paramString() {
402: /*
403: * The format is based on 1.5 release behavior which can be revealed by
404: * the following code: System.out.println(new Choice());
405: */
406:
407: toolkit.lockAWT();
408: try {
409: return (super .paramString() + ",current=" + getSelectedItem()); //$NON-NLS-1$
410: } finally {
411: toolkit.unlockAWT();
412: }
413: }
414:
415: public Object[] getSelectedObjects() {
416: toolkit.lockAWT();
417: try {
418: if (items.size() > 0) {
419: return new Object[] { items.get(selectedIndex) };
420: }
421: return null;
422: } finally {
423: toolkit.unlockAWT();
424: }
425: }
426:
427: public String getItem(int index) {
428: toolkit.lockAWT();
429: try {
430: return items.get(index);
431: } finally {
432: toolkit.unlockAWT();
433: }
434: }
435:
436: public void addItem(String item) {
437: toolkit.lockAWT();
438: try {
439: add(item);
440: } finally {
441: toolkit.unlockAWT();
442: }
443: }
444:
445: /**
446: * @deprecated
447: */
448: @Deprecated
449: public int countItems() {
450: toolkit.lockAWT();
451: try {
452: return getItemCount();
453: } finally {
454: toolkit.unlockAWT();
455: }
456: }
457:
458: public int getItemCount() {
459: toolkit.lockAWT();
460: try {
461: return items.size();
462: } finally {
463: toolkit.unlockAWT();
464: }
465: }
466:
467: public int getSelectedIndex() {
468: toolkit.lockAWT();
469: try {
470: return selectedIndex;
471: } finally {
472: toolkit.unlockAWT();
473: }
474: }
475:
476: public String getSelectedItem() {
477: toolkit.lockAWT();
478: try {
479: if (selectedIndex < 0) {
480: return null;
481: }
482: return items.get(selectedIndex);
483: } finally {
484: toolkit.unlockAWT();
485: }
486: }
487:
488: public void select(int pos) {
489: toolkit.lockAWT();
490: try {
491: if (selectedIndex != pos) { // to avoid dead loop in repaint()
492: if (pos >= items.size() || pos < 0) {
493: // awt.106=specified position is greater than the number of items
494: throw new IllegalArgumentException(Messages
495: .getString("awt.106")); //$NON-NLS-1$
496: }
497: selectedIndex = pos;
498: repaint();
499: }
500: } finally {
501: toolkit.unlockAWT();
502: }
503: }
504:
505: public void select(String str) {
506: toolkit.lockAWT();
507: try {
508: int idx = items.indexOf(str);
509: if (idx >= 0) {
510: select(idx);
511: }
512: } finally {
513: toolkit.unlockAWT();
514: }
515: }
516:
517: @SuppressWarnings("unchecked")
518: @Override
519: public <T extends EventListener> T[] getListeners(
520: Class<T> listenerType) {
521: if (ItemListener.class.isAssignableFrom(listenerType)) {
522: return (T[]) getItemListeners();
523: }
524: return super .getListeners(listenerType);
525: }
526:
527: public void addItemListener(ItemListener l) {
528: itemListeners.addUserListener(l);
529: }
530:
531: public void removeItemListener(ItemListener l) {
532: itemListeners.removeUserListener(l);
533: }
534:
535: public ItemListener[] getItemListeners() {
536: return itemListeners.getUserListeners(new ItemListener[0]);
537: }
538:
539: @Override
540: protected void processEvent(AWTEvent e) {
541: if (toolkit.eventTypeLookup.getEventMask(e) == AWTEvent.ITEM_EVENT_MASK) {
542: processItemEvent((ItemEvent) e);
543: } else {
544: super .processEvent(e);
545: }
546: }
547:
548: protected void processItemEvent(ItemEvent e) {
549: for (Iterator<?> i = itemListeners.getUserIterator(); i
550: .hasNext();) {
551: ItemListener listener = (ItemListener) i.next();
552:
553: switch (e.getID()) {
554: case ItemEvent.ITEM_STATE_CHANGED:
555: listener.itemStateChanged(e);
556: break;
557: }
558: }
559: }
560:
561: @Override
562: ComponentBehavior createBehavior() {
563: return new HWBehavior(this );
564: }
565:
566: @Override
567: String autoName() {
568: return ("choice" + toolkit.autoNumber.nextChoice++); //$NON-NLS-1$
569: }
570:
571: /**
572: * Widest list item must fit into Choice minimum size
573: */
574: @Override
575: Dimension getDefaultMinimumSize() {
576: Dimension minSize = new Dimension();
577: if (!isDisplayable()) {
578: return minSize;
579: }
580: int hGap = 2 * BORDER_SIZE;
581: int vGap = hGap;
582: Font font = getFont();
583: FontMetrics fm = getFontMetrics(font);
584: minSize.height = fm.getHeight() + vGap + 1;
585: minSize.width = hGap + 16; // TODO: use arrow button size
586: Graphics2D gr = (Graphics2D) getGraphics();
587: FontRenderContext frc = gr.getFontRenderContext();
588: int maxItemWidth = 5; // TODO: take width of some char
589: for (int i = 0; i < items.size(); i++) {
590: String item = getItem(i);
591: int itemWidth = font.getStringBounds(item, frc).getBounds().width;
592: if (itemWidth > maxItemWidth) {
593: maxItemWidth = itemWidth;
594: }
595: }
596: minSize.width += maxItemWidth;
597: gr.dispose();
598: return minSize;
599: }
600:
601: @Override
602: boolean isPrepainter() {
603: return true;
604: }
605:
606: @Override
607: void prepaint(Graphics g) {
608: toolkit.theme.drawChoice(g, state);
609: }
610:
611: @Override
612: void setFontImpl(Font f) {
613: super .setFontImpl(f);
614: setSize(getWidth(), getDefaultMinimumSize().height);
615: }
616:
617: @SuppressWarnings("deprecation")
618: int getItemHeight() {
619: FontMetrics fm = toolkit.getFontMetrics(getFont());
620: int itemHeight = fm.getHeight() + 2;
621: return itemHeight;
622: }
623:
624: void fireItemEvent() {
625: postEvent(new ItemEvent(this , ItemEvent.ITEM_STATE_CHANGED,
626: getSelectedItem(), ItemEvent.SELECTED));
627: }
628:
629: /**
630: * This method is necessary because <code> public select(int) </code> should
631: * not fire any events
632: */
633: void selectAndFire(int idx) {
634: if (idx == getSelectedIndex()) {
635: return;
636: }
637: select(idx);
638: fireItemEvent();
639: }
640:
641: /**
642: * Gets the nearest valid index
643: *
644: * @param idx any integer value
645: * @return valid item index nearest to idx
646: */
647: int getValidIndex(int idx) {
648: return Math.min(getItemCount() - 1, Math.max(0, idx));
649: }
650:
651: @Override
652: AccessibleContext createAccessibleContext() {
653: return new AccessibleAWTChoice();
654: }
655: }
|