001: /*
002: * Javu WingS - Lightweight Java Component Set
003: * Copyright (c) 2005-2007 Krzysztof A. Sadlocha
004: * e-mail: ksadlocha@programics.com
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020:
021: package com.javujavu.javux.wings;
022:
023: import java.awt.Container;
024: import java.awt.Dimension;
025: import java.awt.Graphics;
026: import java.awt.IllegalComponentStateException;
027: import java.awt.Image;
028: import java.awt.Insets;
029: import java.awt.ItemSelectable;
030: import java.awt.Point;
031: import java.awt.Rectangle;
032: import java.awt.event.ActionEvent;
033: import java.awt.event.FocusEvent;
034: import java.awt.event.FocusListener;
035: import java.awt.event.ItemEvent;
036: import java.awt.event.KeyEvent;
037: import java.awt.event.MouseEvent;
038: import java.awt.event.MouseListener;
039: import java.awt.event.MouseMotionListener;
040:
041: /**
042: * A component that combines a button or editable field and a drop-down list.
043: * The user can select a value from the drop-down list, which appears at the
044: * user's request. If you make the combo box editable, then the combo box
045: * includes an editable field into which the user can type a value.
046: * <br>
047: * It is a complex component containing <code>WingButton</code>,
048: * <code>WingList</code> and <code>WingTextFiled</code>.<br>
049: * Its drop down list uses also <code>WingScrollPane</code>.<br>
050: * <br>
051: * <b>This class is thread safe.</b>
052: **/
053: public class WingCombo extends WingComponent implements ItemSelectable,
054: MouseListener, MouseMotionListener, FocusListener {
055: private/*final*/WingButton down;
056: private/*final*/WingComponent edit;
057: private/*final*/WingList list;
058: private/*final*/WingScrollPane listPane;
059: private/*final*/WingTimer loser;
060: private/*final*/boolean editable;
061:
062: private Object selectedItem;
063: private Container popupHandle;
064:
065: protected boolean focused;
066: private boolean drag;
067:
068: /**
069: * Creates a <code>WingCombo</code> with not editable field.
070: */
071: public WingCombo() {
072: this (false);
073: }
074:
075: /**
076: * Creates a <code>WingCombo</code> with
077: * editable or not editable field.
078: * @param editable a boolean value, where true indicates that the
079: * combo field is editable
080: */
081: public WingCombo(boolean editable) {
082:
083: loser = new WingTimer(200, false, this );
084: down = new WingButton();
085: this .add(down);
086: list = new WingList(WingList.VERTICAL);
087: list.selectOnMove = true;
088: list.setWingFocusable(false);
089: list.popupOwner = this ;
090: list.addMouseListener(this );
091: list.addFocusListener(this );
092:
093: listPane = new WingScrollPane(list, true);
094: listPane
095: .setHorizontalScrollBarPolicy(WingScrollPane.SCROLLBAR_NEVER);
096: listPane.addFocusListener(this );
097:
098: //add list for a while to force addNotify
099: listPane.setVisible(false);
100: this .add(listPane);
101:
102: this .editable = editable;
103: if (editable) {
104: edit = new WingTextField(4);
105: } else {
106: edit = new Viewer();
107: }
108: edit.setWingFocusable(false);
109: edit.addMouseListener(this );
110: edit.addMouseMotionListener(this );
111: this .add(edit);
112:
113: this .setWingFocusable(true);
114:
115: down.setWingFocusable(false);
116: down.addMouseListener(this );
117: down.addMouseMotionListener(this );
118:
119: this .enableEvents(MouseEvent.MOUSE_EVENT_MASK
120: | MouseEvent.FOCUS_EVENT_MASK);
121: WingToolkit.the().addWheelListener(this , this );
122:
123: setStyleId(null);
124: }
125:
126: public void setStyleId(String styleId) {
127: super .setStyleId(styleId);
128: String styleCombo = WingSkin.catKeys(styleId, "combo");
129: down.setStyleId(styleCombo);
130: list.setStyleId(styleCombo);
131: listPane.setStyleId(styleCombo);
132: edit.setStyleId(styleCombo);
133: }
134:
135: /**
136: * Loads skin resources of this and all child components.<br>
137: * <pre>
138: * style ID of child components:
139: * button - [optional styleID.]combo
140: * list - [optional styleID.]combo
141: * textfield - [optional styleID.]combo
142: * non editable viewer - [optional styleID.]doc
143: * </pre>
144: * @see Style
145: * @see WingSkin
146: */
147: public void loadSkin() {
148: super .loadSkin();
149: WingComponent.updateSkin(listPane);
150: }
151:
152: public void setEnabled(boolean b) {
153: super .setEnabled(b);
154: edit.setEnabled(b);
155: down.setEnabled(b);
156: }
157:
158: /**
159: * Returns editor WingTextFiled or null if not editable
160: * @return editor WingTextFiled or null if not editable
161: */
162: public WingTextField getEditor() {
163: return (editable) ? ((WingTextField) edit) : null;
164: }
165:
166: /**
167: * @see WingComponent#setTooltip(java.lang.Object)
168: */
169: public void setTooltip(Object tooltip) {
170: edit.setTooltip(tooltip);
171: down.setTooltip(tooltip);
172: }
173:
174: /**
175: * Adds an item to the item list.
176: * It invokes <code>insertItem(item,-1)</code>
177: * @param item the Object to add to the list
178: */
179: public void addItem(Object item) {
180: insertItem(item, -1);
181: }
182:
183: /**
184: * Inserts an item into the item list at a given index.
185: * This method allows negative index values.
186: * The item with negative index is inserted at <code>getItemCount()+index+1</code>
187: * which means at the end of the list for index = -1
188: * @param item the <code>Object</code> to add to the list
189: * @param index an integer specifying the position at which
190: * to add the item
191: *
192: */
193: public synchronized void insertItem(Object item, int index) {
194: list.insertItem(item, index);
195: if (getItemCount() == 1)
196: setSelectedIndex(0);
197: revalidateAndRepaint();
198: }
199:
200: /**
201: * Removes the item at <code>index</code>
202: * @param index an int specifying the index of the item to remove
203: */
204: public synchronized void removeItem(int index) {
205: boolean changeSelection = (selectedItem == list.getItem(index));
206: list.removeItem(index);
207: if (changeSelection)
208: selectedItem = list.getSelectedItem();
209: revalidateAndRepaint();
210: }
211:
212: /**
213: * Removes all items from the item list.
214: */
215: public synchronized void removeAllItems() {
216: list.removeAllItems();
217: setSelectedItem(null);
218: revalidateAndRepaint();
219: }
220:
221: /**
222: * Returns the number of items in the list.
223: *
224: * @return an integer equal to the number of items in the list
225: */
226: public int getItemCount() {
227: return list.getItemCount();
228: }
229:
230: /**
231: * Returns the list item at the specified index. If <code>index</code>
232: * is out of range (less than zero or greater than or equal to size)
233: * it will return <code>null</code>.
234: *
235: * @param index an integer indicating the list position
236: * @return the <code>Object</code> at that list position; or
237: * <code>null</code> if out of range
238: */
239: public Object getItem(int index) {
240: return list.getItem(index);
241: }
242:
243: /**
244: * Gets the index of the selected item on the list,
245: *
246: * @return the index of the selected item;
247: * if no item is selected <code>-1</code> is returned.
248: */
249: public synchronized int getSelectedIndex() {
250: return (isOpen()) ? ((selectedItem != null) ? list
251: .indexOf(selectedItem) : -1) : list.getSelectedIndex();
252: }
253:
254: /**
255: * Selects the item at the specified index in the list.
256: *
257: * @param index the position of the item to select
258: */
259: public synchronized void setSelectedIndex(int index) {
260: list.setSelectedIndex(index);
261: Object newItem = list.getSelectedItem();
262: if (newItem != selectedItem) {
263: if (editable)
264: ((WingTextField) edit)
265: .setText((newItem != null) ? newItem.toString()
266: : null);
267: else
268: edit.repaint();
269: selectedItem = newItem;
270: }
271: }
272:
273: /**
274: * Gets the selected item on the list.
275: *
276: * @return the selected item on the list;
277: * if no item is selected <code>null</code> is returned.
278: */
279: public synchronized Object getSelectedItem() {
280: Object r = list.getSelectedItem();
281: if (editable) {
282: String s = ((WingTextField) edit).getText();
283: if ((r == null && s.length() > 0)
284: || (r != null && !s.equals(r.toString())))
285: r = s;
286: }
287: return r;
288: }
289:
290: /**
291: * Sets the selected item in the combo box display area to the item in
292: * the argument.
293: * If <code>item</code> is in the list, the display area shows
294: * <code>item</code> selected.
295: * <p>
296: * If <code>item</code> is not in the list and the combo box is
297: * editable the selection will change to <code>item.toString()</code>.
298: * @param item the list item to select; use <code>null</code> to
299: * clear the selection
300: */
301: public synchronized void setSelectedItem(Object item) {
302: list.setSelectedItem(item);
303: selectedItem = item;
304: if (editable) {
305: ((WingTextField) edit).setText((item != null) ? item
306: .toString() : null);
307: } else
308: edit.repaint();
309: }
310:
311: private void shiftByUser(int shift) {
312: synchronized (this ) {
313: int index = getSelectedIndex();
314: index += shift;
315: if (index < 0 || index >= getItemCount())
316: return;
317: setSelectedIndex(index);
318: }
319: postItemEvent();
320: }
321:
322: /**
323: * Gets the selected items on the list in an array of Objects.
324: * @return an array of <code>Object</code>s representing the
325: * selected items on the list;
326: * if no item is selected, empty array is returned.
327: */
328: public Object[] getSelectedObjects() {
329: Object selected = getSelectedItem();
330: if (selected == null)
331: return new Object[0];
332: else {
333: Object[] rr = new Object[1];
334: rr[0] = selected;
335: return rr;
336: }
337: }
338:
339: private void postItemEvent() {
340: wingProcessItemEvent(new ItemEvent(this ,
341: ItemEvent.ITEM_STATE_CHANGED, list.getSelectedItem(),
342: ItemEvent.SELECTED));
343: }
344:
345: ///////////////////////////////////////////////////////////
346:
347: public Dimension getPreferredSize() {
348: Dimension prefSize = wingPrefSize;
349: if (prefSize == null) {
350: prefSize = new Dimension(down.getPreferredSize());
351: Dimension d2 = edit.getPreferredSize();
352: Dimension d3 = list.getPreferredSize();
353: prefSize.width += (d2.width > d3.width) ? d2.width
354: : d3.width;
355: prefSize.height = d2.height;
356: wingPrefSize = prefSize;
357: }
358: return prefSize;
359: }
360:
361: public void doLayout() {
362: Dimension d = getSize();
363: Dimension d2 = down.getPreferredSize();
364: if (edit != null)
365: edit.setBounds(0, 0, d.width - d2.width, d.height);
366: down.setBounds(d.width - d2.width, 0, d2.width, d.height);
367: }
368:
369: //////////////////////////////////////////////////////////////
370: // popup
371: /**
372: * <strong>invoke on event thread only!</strong><br>
373: * <br><br><strong>This method acquire TreeLock</strong>
374: */
375: private void openList() {
376: if (isOpen() || getItemCount() == 0)
377: return;
378:
379: Dimension minLightSize = new Dimension(getPreferredSize());
380: minLightSize.height *= 3;
381: Dimension size = getSize();
382:
383: remove(listPane);
384: listPane.setVisible(true);
385:
386: Container handle = WingToolkit.showPopup(this , 0, 0,
387: size.width, size.height, HORIZONTAL, size.width, 0,
388: listPane, minLightSize, lightPopups, heavyPopups,
389: !heavyPopups, false);
390: if (handle != null) {
391: list.scrollToSelected();
392: setPopup(this );
393: if (handle != listPane) {
394: // handle.addFocusListener(this);
395: list.wingRequestFocusInWindow();
396: }
397: }
398: popupHandle = handle;
399: }
400:
401: /**
402: * <strong>invoke on event thread only!</strong><br>
403: * <br><br><strong>This method acquire TreeLock</strong>
404: */
405: private void closeList(boolean accept, boolean requestFocus) {
406: if (!isOpen())
407: return;
408:
409: Container handle = popupHandle;
410: popupHandle = null;
411:
412: if (requestFocus && handle != listPane) {
413: requestFocus();
414: }
415: WingToolkit.hidePopup(handle);
416: clrPopup(this );
417:
418: if (accept) {
419: synchronized (this ) {
420: Object listSelected = list.getSelectedItem();
421: if (listSelected == selectedItem)
422: return;
423: setSelectedItem(listSelected);
424: }
425: postItemEvent();
426: } else {
427: list.setSelectedItem(selectedItem);
428: }
429: }
430:
431: private boolean isOpen() {
432: return popupHandle != null;
433: }
434:
435: ///////////////////////////////////////////////////////
436: // actions
437:
438: protected void processFocusEvent(FocusEvent e) {
439: if (e.getID() == FocusEvent.FOCUS_LOST) {
440: focused = false;
441: if (isOpen()) {
442: if (popupHandle != listPane) {
443: loser.start();
444: return; //do not dispatch
445: } else {
446: closeList(false, false);
447: }
448: }
449: } else {
450: focused = true;
451: loser.stop();
452: }
453: if (editable) {
454: ((WingTextField) edit).processFocusEvent(e);
455: }
456: down.processFocusEvent(e);
457: repaint();
458:
459: super .processFocusEvent(e);
460: }
461:
462: public void focusGained(FocusEvent e) {
463: loser.stop();
464: // if(e.getSource()==popupHandle)
465: // {
466: // list.requestFocus();
467: // }
468: }
469:
470: public void focusLost(FocusEvent e) {
471: // if(e.getSource()==list)
472: // {
473: loser.start();
474: // }
475: }
476:
477: /**
478: * @see WingComponent#wingProcessActionEvent(java.awt.event.ActionEvent)
479: */
480: public void wingProcessActionEvent(ActionEvent e) {
481: Object src = e.getSource();
482: if (src == down) {
483: } else if (src == edit) {
484: String text;
485: synchronized (this ) {
486: text = ((WingTextField) edit).getText();
487: if (selectedItem != null
488: && text.equals(selectedItem.toString())) {
489: return;
490: }
491: }
492: setSelectedItem(text);
493: postItemEvent();
494: } else if (src == loser) {
495: if (!focused && !list.focused) {
496: closeList(false, false);
497: processFocusEvent(new FocusEvent(this ,
498: FocusEvent.FOCUS_LOST));
499: }
500: } else
501: super .wingProcessActionEvent(e);
502: }
503:
504: /**
505: * @see WingComponent#wingProcessItemEvent(java.awt.event.ItemEvent)
506: */
507: public void wingProcessItemEvent(ItemEvent e) {
508: if (e.getSource() != list)
509: super .wingProcessItemEvent(e);
510: }
511:
512: /**
513: * @see WingComponent#cancelPopup(com.javujavu.javux.wings.WingComponent)
514: */
515: public void cancelPopup(WingComponent src) {
516: Container ph = popupHandle;
517: if (ph == null || ph.isAncestorOf(src) || src == down
518: || src == edit) {
519: return;
520: }
521: closeList(false, false);
522: }
523:
524: /**
525: * @see WingComponent#wingProcessKeyEvent(java.awt.event.KeyEvent, com.javujavu.javux.wings.WingComponent)
526: */
527: protected void wingProcessKeyEvent(KeyEvent e,
528: WingComponent redirecting) {
529: if (redirecting == this )
530: return;
531:
532: if (e.getID() == KeyEvent.KEY_PRESSED) {
533: boolean consume = true;
534: int key = e.getKeyCode();
535: int mask = e.getModifiers();
536: boolean open = isOpen();
537:
538: if (!open && key == KeyEvent.VK_UP && mask == 0) {
539: shiftByUser(-1);
540: } else if (!open && key == KeyEvent.VK_DOWN && mask == 0) {
541: shiftByUser(+1);
542: } else if (open
543: && (((key == KeyEvent.VK_ENTER
544: || key == KeyEvent.VK_SPACE || key == KeyEvent.VK_ESCAPE) && mask == 0) || ((key == KeyEvent.VK_UP || key == KeyEvent.VK_DOWN) && mask == KeyEvent.ALT_MASK))) {
545: closeList(key != KeyEvent.VK_ESCAPE, true);
546: } else if (((key == KeyEvent.VK_UP || key == KeyEvent.VK_DOWN) && mask == KeyEvent.ALT_MASK)
547: || (key == KeyEvent.VK_SPACE && !editable)) {
548: openList();
549: } else
550: consume = false;
551: if (consume)
552: e.consume();
553: }
554:
555: if (e.getSource() == this ) {
556: if (isOpen())
557: list.wingProcessKeyEvent(e, this );
558: else if (editable)
559: edit.wingProcessKeyEvent(e, this );
560: }
561:
562: super .wingProcessKeyEvent(e, redirecting);
563: }
564:
565: /*
566: * @see WingComponent#mouseWheelMoved(java.lang.Object, int)
567: */
568: public void mouseWheelMoved(Object src, int rotation) {
569: if (isEnabled())
570: shiftByUser(((rotation > 0) ? 1 : -1));
571: }
572:
573: public void mouseClicked(MouseEvent e) {
574: }
575:
576: public void mouseEntered(MouseEvent e) {
577: }
578:
579: public void mouseExited(MouseEvent e) {
580: }
581:
582: public void mousePressed(MouseEvent e) {
583: Object src = e.getSource();
584: if (src == list) {
585: closeList(true, true);
586: } else {
587: if (wingFocusable && !focused)
588: requestFocus(); //wingRequestFocusInWindow();
589: if ((src != edit || !editable || isOpen()) && isEnabled()) {
590: if (isOpen())
591: closeList(false, true);
592: else
593: openList();
594: }
595: }
596: }
597:
598: public void mouseReleased(MouseEvent e) {
599: if (drag && e.getSource() != list) {
600: closeList(true, true);
601: }
602: drag = false;
603: }
604:
605: public void mouseMoved(MouseEvent e) {
606: }
607:
608: public void mouseDragged(MouseEvent e) {
609: if (!isOpen())
610: return;
611: Point psrc, plist, ppane, p;
612: Dimension size;
613: try {
614: psrc = ((WingComponent) e.getSource())
615: .getLocationOnScreen();
616: plist = list.getLocationOnScreen();
617: ppane = listPane.getLocationOnScreen();
618: size = listPane.getSize();
619: } catch (IllegalComponentStateException ec) {
620: return;
621: }
622:
623: p = e.getPoint();
624: int y;
625: y = p.y + psrc.y;
626: if (y >= ppane.y && y < ppane.y + size.height) {
627: list.mouseSelect(new Point(0, y - plist.y));
628: drag = true;
629: }
630: }
631:
632: ///////////////////////////////////////////////////////
633:
634: /**
635: * Class used in combo instead of <code>WingTextField</code>
636: * when the combo is uneditable
637: */
638: protected class Viewer extends WingComponent {
639: private Style stFocused;
640:
641: public void loadSkin() {
642: stNormal = WingSkin.getStyle(styleId, null, DOC | NORMAL,
643: null);
644: stFocused = WingSkin.getStyle(styleId, null, DOC | FOCUSED,
645: stNormal);
646: stDisabled = WingSkin.getStyle(styleId, null, DOC
647: | DISABLED, stNormal);
648: }
649:
650: public Style getStyle() {
651: return (focused) ? stFocused : super .getStyle();
652: }
653:
654: public Dimension getPreferredSize() {
655: Dimension d = new Dimension(list.getPrefItemSize());
656: d.height += stNormal.margin.top + stNormal.margin.bottom;
657: d.width += stNormal.margin.left + stNormal.margin.right;
658: return d;
659: }
660:
661: public void wingPaint(Graphics g) {
662: Dimension d = getSize();
663: Insets m = stNormal.margin;
664: Style st = list.itemStyles.get(isEnabled(), focused,
665: focused, false);
666: list.getItemRenderer().drawItem(g, m.left, m.top,
667: d.width - m.left - m.right,
668: d.height - m.top - m.bottom, selectedItem, this ,
669: st, st.margin, LEFT, RIGHT, null);
670: }
671:
672: public boolean imageUpdate(Image img, int infoflags, int x,
673: int y, int width, int height) {
674: if (!isShowing() || (infoflags & ABORT) != 0)
675: return false;
676:
677: return list.getRenderer().imageUpdate(
678: img,
679: infoflags,
680: x,
681: y,
682: width,
683: height,
684: selectedItem,
685: this ,
686: list.itemStyles.get(isEnabled(), focused, focused,
687: false), new Rectangle(getSize()));
688: }
689: }
690: }
|