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.AWTEvent;
024: import java.awt.Dimension;
025: import java.awt.Graphics;
026: import java.awt.ItemSelectable;
027: import java.awt.Point;
028: import java.awt.Rectangle;
029: import java.awt.event.FocusEvent;
030: import java.awt.event.ItemEvent;
031: import java.awt.event.KeyEvent;
032: import java.awt.event.MouseEvent;
033: import java.util.Vector;
034: import com.javujavu.javux.wings.item.DefaultItemRenderer;
035: import com.javujavu.javux.wings.item.ItemRenderer;
036: import com.javujavu.javux.wings.item.ItemStyles;
037:
038: /**
039: * A component that displays a list of objects and allows the user to select
040: * one item.
041: * <br>
042: * <b>This class is thread safe.</b>
043: **/
044: public class WingList extends WingComponent implements ItemSelectable {
045: /** Indicates a vertical layout of cells, in a single column */
046: public static final int VERTICAL = 0;
047: /** Indicates a layout with cells flowing vertically then horizontally */
048: public static final int VERTICAL_WRAP = 1;
049: /** Indicates a layout with cells flowing horizontally then vertically */
050: public static final int HORIZONTAL_WRAP = 2;
051:
052: protected ItemStyles itemStyles;
053: private ItemRenderer itemRenderer;
054:
055: private Vector items;
056: protected int selectedIndex;
057:
058: private int rows;
059: private int cols;
060: protected Dimension prefItemSize;
061: protected Dimension itemSize;
062:
063: protected boolean selectOnMove;
064: private int layout;
065: private Dimension lastViewSize;
066: protected boolean focused;
067:
068: /**
069: * Creates a new empty list
070: */
071: public WingList() {
072: this (VERTICAL);
073: }
074:
075: /**
076: * Creates a list with specified layout
077: * @param layout list layout
078: */
079: public WingList(int layout) {
080: this .layout = layout;
081: items = new Vector();
082: rows = cols = 1;
083: selectedIndex = -1;
084: setWingFocusable(true);
085: enableEvents(AWTEvent.MOUSE_EVENT_MASK
086: | AWTEvent.MOUSE_MOTION_EVENT_MASK
087: | AWTEvent.FOCUS_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
088: }
089:
090: /**
091: * Loads skin resources.<br>
092: * <pre>
093: * styles:
094: * [optional styleID.]list.doc.normal
095: * [optional styleID.]list.doc.disabled
096: * item styles:
097: * [optional styleID.]list.item.normal
098: * [optional styleID.]list.item.dark
099: * [optional styleID.]list.item.disabled
100: * [optional styleID.]list.item.disabled.selected
101: * [optional styleID.]list.item.selected
102: * [optional styleID.]list.item.selected.focused
103: * [optional styleID.]list.item.focused
104: * </pre>
105: * @see Style
106: * @see WingSkin
107: */
108: public void loadSkin() {
109: String idt = WingSkin.catKeys(styleId, "list");
110:
111: stNormal = WingSkin.getStyle(idt, null, DOC | NORMAL, null);
112: stDisabled = WingSkin.getStyle(idt, null, DOC | DISABLED,
113: stNormal).merge(stTop);
114: stNormal = stNormal.merge(stTop);
115: itemStyles = new ItemStyles(idt, stTop);
116:
117: prefItemSize = null;
118: }
119:
120: /**
121: * Sets the renderer that paints list items.
122: * @param itemRenderer the <code>ItemRenderer</code> of the list items;
123: * use null to reset to default
124: * @see ItemRenderer
125: * @see DefaultItemRenderer
126: */
127: public void setItemRenderer(ItemRenderer itemRenderer) {
128: this .itemRenderer = itemRenderer;
129: revalidateAndRepaint();
130: }
131:
132: /**
133: * Returns the renderer used to display list items.
134: * @return the current <code>{@link ItemRenderer}</code> of the list items or of the component.
135: * @see ItemRenderer
136: * @see DefaultItemRenderer
137: */
138: public ItemRenderer getItemRenderer() {
139: ItemRenderer r = itemRenderer;
140: if (r == null)
141: r = getRenderer();
142: return r;
143: }
144:
145: /**
146: * Adds an item to the item list.
147: * It invokes <code>insertItem(item,-1)</code>
148: * @param item the Object to add to the list
149: */
150: public void addItem(Object item) {
151: insertItem(item, -1);
152: }
153:
154: /**
155: * Inserts an item into the item list at a given index.
156: * This method allows negative index values.
157: * The item with negative index is inserted at <code>getItemCount()+index+1</code>
158: * which means at the end of the list for index = -1
159: * @param item the <code>Object</code> to add to the list
160: * @param index an integer specifying the position at which
161: * to add the item
162: *
163: */
164: public synchronized void insertItem(Object item, int index) {
165: int isize = items.size();
166: if (index < 0)
167: index = isize + index + 1;
168: if (index == -1 || index > isize)
169: index = isize;
170: if (index < 0)
171: index = 0;
172: items.insertElementAt(item, index);
173: if (index <= selectedIndex)
174: selectedIndex++;
175: if (prefItemSize != null) {
176: int[] context = { index };
177: Dimension di = getItemRenderer().getItemSize(item, this ,
178: itemStyles.normal, context);
179: if (di.height > prefItemSize.height)
180: prefItemSize.height = di.height;
181: if (di.width > prefItemSize.width)
182: prefItemSize.width = di.width;
183: }
184: revalidateAndRepaint();
185: }
186:
187: /**
188: * Returns the list item at the specified index. If <code>index</code>
189: * is out of range (less than zero or greater than or equal to size)
190: * it will return <code>null</code>.
191: *
192: * @param index an integer indicating the list position
193: * @return the <code>Object</code> at that list position; or
194: * <code>null</code> if out of range
195: */
196: public synchronized Object getItem(int index) {
197: if (index < 0 || index >= items.size())
198: return null;
199: return items.elementAt(index);
200: }
201:
202: /**
203: * Returns the index of the first occurrence of the specified item
204: * in this list, or -1 if this list does not contain the item.
205: * @param item item to search for
206: * @return the index of the first occurrence of the specified item in
207: * this list, or -1 if this list does not contain the item
208: */
209: public int indexOf(Object item) {
210: return items.indexOf(item);
211: }
212:
213: /**
214: * Removes all items from the item list.
215: */
216: public synchronized void removeAllItems() {
217: items.setSize(0);
218: prefItemSize = null;
219: selectedIndex = -1;
220: revalidateAndRepaint();
221: }
222:
223: /**
224: * Removes the item at <code>index</code>
225: * @param index an int specifying the index of the item to remove
226: */
227: public synchronized void removeItem(int index) {
228: if (index < 0 || index > items.size())
229: return;
230: items.removeElementAt(index);
231: if (index < selectedIndex)
232: selectedIndex--;
233: if (selectedIndex >= items.size())
234: selectedIndex = items.size() - 1;
235: prefItemSize = null;
236: revalidateAndRepaint();
237: }
238:
239: /**
240: * Removes the first occurrence of an item from the list.
241: * @param item the item to remove from the list
242: */
243: public synchronized void removeItem(Object item) {
244: removeItem(indexOf(item));
245: }
246:
247: /**
248: * Returns the number of items in the list.
249: *
250: * @return an integer equal to the number of items in the list
251: */
252: public int getItemCount() {
253: return items.size();
254: }
255:
256: /**
257: * Gets the selected item on the list.
258: *
259: * @return the selected item on the list;
260: * if no item is selected <code>null</code> is returned.
261: */
262: public synchronized Object getSelectedItem() {
263: return (selectedIndex != -1) ? items.elementAt(selectedIndex)
264: : null;
265: }
266:
267: /**
268: * Gets the index of the selected item on the list,
269: *
270: * @return the index of the selected item;
271: * if no item is selected <code>-1</code> is returned.
272: */
273: public int getSelectedIndex() {
274: return selectedIndex;
275: }
276:
277: public synchronized Dimension getPrefItemSize() {
278: if (prefItemSize == null) {
279: Dimension d = new Dimension(1, 1);
280: Dimension di;
281:
282: ItemRenderer renderer = getItemRenderer();
283: int[] context = new int[1];
284:
285: for (int i = 0; i < items.size(); i++) {
286: context[0] = i;
287: di = renderer.getItemSize(items.elementAt(i), this ,
288: itemStyles.normal, context);
289: if (di.height > d.height)
290: d.height = di.height;
291: if (di.width > d.width)
292: d.width = di.width;
293: }
294: prefItemSize = d;
295: }
296: return prefItemSize;
297: }
298:
299: public Dimension getPreferredSize() {
300: Dimension viewSize = getViewOrSize();
301: Dimension prefSize = wingPrefSize;
302: if (prefSize == null
303: || (!wingPrefSizeSet && (prefItemSize == null
304: || lastViewSize == null || !lastViewSize
305: .equals(viewSize)))) {
306: Dimension prefItemSize = getPrefItemSize();
307: int itemCount = items.size();
308:
309: prefSize = new Dimension();
310: if (layout == VERTICAL) {
311: prefSize.width = prefItemSize.width;
312: prefSize.height = prefItemSize.height * itemCount;
313: } else if (layout == HORIZONTAL_WRAP) {
314: int colums = viewSize.width / prefItemSize.width;
315: if (colums <= 0)
316: colums = 1;
317: prefSize.width = colums * prefItemSize.width;
318: prefSize.height = prefItemSize.height
319: * ((itemCount + colums - 1) / colums);
320: } else if (layout == VERTICAL_WRAP) {
321: int rows = viewSize.height / prefItemSize.height;
322: if (rows <= 0)
323: rows = 1;
324: prefSize.height = rows * prefItemSize.height;
325: prefSize.width = prefItemSize.width
326: * ((itemCount + rows - 1) / rows);
327: }
328:
329: wingPrefSize = prefSize;
330: lastViewSize = viewSize;
331: }
332: return prefSize;
333: }
334:
335: public synchronized void doLayout() {
336: Dimension prefItemSize = getPrefItemSize();
337: Dimension itemSize = new Dimension(prefItemSize);
338: Dimension size = getSize();
339: int columns = 1, rows = 1;
340: int width = size.width;
341: int height = size.height;
342:
343: if (layout == HORIZONTAL_WRAP) {
344: columns = width / prefItemSize.width;
345: if (columns <= 0)
346: columns = 1;
347: } else if (layout == VERTICAL_WRAP) {
348: rows = height / prefItemSize.height;
349: if (rows <= 0)
350: rows = 1;
351: } else //if(layout==VERTICAL)
352: {
353: itemSize.width = width;
354: }
355:
356: this .itemSize = itemSize;
357: this .cols = columns;
358: this .rows = rows;
359: }
360:
361: /**
362: * @see com.javujavu.javux.wings.WingComponent#getScrollIncrements(java.awt.Point, java.awt.Point)
363: */
364: public void getScrollIncrements(Point unit, Point block) {
365: Dimension itemSize = this .itemSize;
366: if (itemSize == null)
367: return;
368: unit.x = itemSize.width / 3;
369: unit.y = itemSize.height;
370: Dimension viewSize = getViewOrSize();
371: block.x = viewSize.width - itemSize.width / 3;
372: block.y = (viewSize.height / itemSize.height - 1)
373: * itemSize.height;
374: }
375:
376: /**
377: * @see com.javujavu.javux.wings.WingComponent#wingPaint(java.awt.Graphics)
378: */
379: public synchronized void wingPaint(Graphics g) {
380: Rectangle oldClip = g.getClipBounds();
381: Rectangle rect = (oldClip != null) ? oldClip : new Rectangle(
382: getSize());
383: Rectangle itemClip = new Rectangle();
384:
385: int col0, cole, row0, rowe;
386: col0 = rect.x / itemSize.width;
387: cole = (rect.x + rect.width + itemSize.width - 1)
388: / itemSize.width - 1;
389: row0 = rect.y / itemSize.height;
390: rowe = (rect.y + rect.height + itemSize.height - 1)
391: / itemSize.height - 1;
392:
393: int x, y, i, xp, yp;
394:
395: if (layout == VERTICAL_WRAP) {
396: if (rowe >= rows)
397: rowe = rows - 1;
398: } else {
399: if (cole >= cols)
400: cole = cols - 1;
401: }
402:
403: ItemRenderer renderer = getItemRenderer();
404: int[] context = new int[1];
405:
406: for (x = col0; x <= cole; x++) {
407: xp = x * itemSize.width;
408: for (y = row0; y <= rowe; y++) {
409: yp = y * itemSize.height;
410: if (layout == VERTICAL_WRAP)
411: i = y + x * rows;
412: else
413: i = y * cols + x;
414:
415: if (i >= 0 && i < items.size()) {
416: int width = itemSize.width;
417: int height = itemSize.height;
418: itemClip.setBounds(xp, yp, width, height);
419: WingToolkit.intersection(itemClip, rect);
420: g.setClip(itemClip);
421:
422: Object item = items.elementAt(i);
423: boolean selected = (i == selectedIndex);
424: boolean dark = ((y % 2) ^ (x % 2)) != 0;
425: boolean focused = this .focused && selected;
426:
427: Style st = itemStyles.get(isEnabled(), selected,
428: focused, dark);
429: context[0] = i;
430: renderer.drawItem(g, xp, yp, itemSize.width,
431: itemSize.height, item, this , st, st.margin,
432: LEFT, RIGHT, context);
433: }
434: }
435: }
436: g.setClip(oldClip);
437: }
438:
439: /**
440: * Gets the selected items on the list in an array of Objects.
441: * @return an array of <code>Object</code>s representing the
442: * selected items on the list;
443: * if no item is selected, empty array is returned.
444: */
445: public Object[] getSelectedObjects() {
446: Object o = getSelectedItem();
447: Object[] r;
448: if (o != null) {
449: r = new Object[1];
450: r[0] = o;
451: } else
452: r = new Object[0];
453: return r;
454: }
455:
456: protected void wingProcessMouseEvent(MouseEvent e) {
457: int id = e.getID();
458: if (id == MouseEvent.MOUSE_PRESSED && isEnabled()) {
459: if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
460: mouseSelect(e.getPoint());
461: if (wingFocusable && !focused)
462: requestFocus(); //wingRequestFocusInWindow();
463: }
464: } else if ((id == MouseEvent.MOUSE_DRAGGED || (id == MouseEvent.MOUSE_MOVED && selectOnMove))
465: && isEnabled()) {
466: mouseSelect(e.getPoint());
467: }
468: }
469:
470: protected void mouseSelect(Point p) {
471: selectedByUser(locationToIndex(p));
472: }
473:
474: /**
475: * Sets the selected item in the combo box display area to the item in
476: * the argument.
477: * If <code>item</code> is in the list, the display area shows
478: * <code>item</code> selected.
479: * <p>
480: * @param item the list item to select; use <code>null</code> to
481: * clear the selection
482: */
483: public synchronized void setSelectedItem(Object item) {
484: int index = (item != null) ? items.indexOf(item) : -1;
485: setSelectedIndex(index);
486: }
487:
488: /**
489: * Selects the item at the specified index in the list.
490: *
491: * @param index the position of the item to select
492: */
493: public synchronized void setSelectedIndex(int index) {
494: if (index == selectedIndex || index < -1
495: || index >= items.size())
496: return;
497: selectedIndex = index;
498: repaint();
499: }
500:
501: /**
502: * <br><br><strong>This method acquire TreeLock</strong>
503: */
504: protected void selectedByUser(int index) {
505: Object item;
506: synchronized (this ) {
507: if (index < 0 || index > items.size())
508: index = items.size() - 1;
509: int old = selectedIndex;
510: setSelectedIndex(index);
511: if (old == selectedIndex)
512: return;
513: item = getSelectedItem();
514: }
515: scrollToSelected();
516: postOnEventThread(new ItemEvent(this ,
517: ItemEvent.ITEM_STATE_CHANGED, item, ItemEvent.SELECTED));
518: }
519:
520: /**
521: * <br><br><strong>This method acquire TreeLock</strong>
522: */
523: protected void scrollToSelected() {
524: Rectangle rect = getItemBounds(selectedIndex);
525: if (rect != null)
526: scrollRectToVisible(rect);
527: }
528:
529: /**
530: * Returns the cell index at the given location in the list
531: * coordinate system.
532: * @param p the coordinates of the point
533: * @return the cell index at the given location, or -1
534: */
535: public synchronized int locationToIndex(Point p) {
536: if (itemSize == null)
537: return -1;
538: int x = p.x / itemSize.width;
539: int y = p.y / itemSize.height;
540: int i;
541: if (x < 0)
542: x = 0;
543: if (y < 0)
544: y = 0;
545: if (layout == VERTICAL_WRAP) {
546: if (y >= rows)
547: y = rows - 1;
548: i = y + x * rows;
549: } else {
550: if (x >= cols)
551: x = cols - 1;
552: i = y * cols + x;
553: }
554: if (i >= items.size())
555: i = -1;
556: return i;
557: }
558:
559: /**
560: * Returns the bounding rectangle, in the list coordinate system,
561: * for the cell specified by the index.
562: * @param index the index
563: * @return the bounding rectangle for the cell, or <code>null</code>
564: */
565: public synchronized Rectangle getItemBounds(int index) {
566: if (itemSize == null || index < 0)
567: return null;
568: if (layout == VERTICAL_WRAP) {
569: return new Rectangle((index / rows) * itemSize.width,
570: (index % rows) * itemSize.height, itemSize.width,
571: itemSize.height);
572: } else {
573: return new Rectangle((index % cols) * itemSize.width,
574: (index / cols) * itemSize.height, itemSize.width,
575: itemSize.height);
576: }
577: }
578:
579: protected void wingProcessKeyEvent(KeyEvent e,
580: WingComponent redirecting) {
581: int key = e.getKeyCode();
582: if (e.getID() == KeyEvent.KEY_PRESSED
583: && e.getModifiers() == 0
584: && (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_RIGHT
585: || key == KeyEvent.VK_UP
586: || key == KeyEvent.VK_DOWN
587: || key == KeyEvent.VK_PAGE_UP || key == KeyEvent.VK_PAGE_DOWN)) {
588: int newIndex;
589: synchronized (this ) {
590: int i = selectedIndex;
591: int y = 0, x = 0;
592:
593: if (key == KeyEvent.VK_PAGE_UP
594: || key == KeyEvent.VK_PAGE_DOWN) {
595: int d = getViewOrSize().height / itemSize.height
596: - 1;
597: if (key == KeyEvent.VK_PAGE_DOWN) {
598: i += d;
599: if (i >= items.size())
600: i = items.size() - 1;
601: } else {
602: i -= d;
603: if (i < 0)
604: i = 0;
605: }
606: }
607: if (i == -1)
608: i = 0;
609:
610: if (layout == VERTICAL_WRAP) {
611: x = i / rows;
612: y = i % rows;
613: } else {
614: x = i % cols;
615: y = i / cols;
616: }
617:
618: if (key == KeyEvent.VK_LEFT)
619: x--;
620: else if (key == KeyEvent.VK_RIGHT)
621: x++;
622: else if (key == KeyEvent.VK_UP)
623: y--;
624: else if (key == KeyEvent.VK_DOWN)
625: y++;
626:
627: if (y < 0) {
628: if (layout == VERTICAL_WRAP && x > 0) {
629: y = rows - 1;
630: x--;
631: } else
632: y = 0;
633: }
634: if (x < 0)
635: x = 0;
636: if (layout == VERTICAL_WRAP) {
637: if (y >= rows) {
638: y = 0;
639: x++;
640: }
641: } else {
642: if (x >= cols)
643: x = cols - 1;
644: }
645: if (layout == VERTICAL_WRAP)
646: newIndex = y + x * rows;
647: else
648: newIndex = y * cols + x;
649: }
650: selectedByUser(newIndex);
651: e.consume();
652: }
653: super .wingProcessKeyEvent(e, redirecting);
654: }
655:
656: protected void processFocusEvent(FocusEvent e) {
657: focused = (e.getID() == FocusEvent.FOCUS_GAINED);
658: if (focused && selectedIndex == -1) {
659: selectedByUser(0);
660: }
661: repaint();
662: super.processFocusEvent(e);
663: }
664: }
|