0001: /* *************************************************************************
0002:
0003: Millstone(TM)
0004: Open Sourced User Interface Library for
0005: Internet Development with Java
0006:
0007: Millstone is a registered trademark of IT Mill Ltd
0008: Copyright (C) 2000-2005 IT Mill Ltd
0009:
0010: *************************************************************************
0011:
0012: This library is free software; you can redistribute it and/or
0013: modify it under the terms of the GNU Lesser General Public
0014: license version 2.1 as published by the Free Software Foundation.
0015:
0016: This library is distributed in the hope that it will be useful,
0017: but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: Lesser General Public License for more details.
0020:
0021: You should have received a copy of the GNU Lesser General Public
0022: License along with this library; if not, write to the Free Software
0023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0024:
0025: *************************************************************************
0026:
0027: For more information, contact:
0028:
0029: IT Mill Ltd phone: +358 2 4802 7180
0030: Ruukinkatu 2-4 fax: +358 2 4802 7181
0031: 20540, Turku email: info@itmill.com
0032: Finland company www: www.itmill.com
0033:
0034: Primary source for MillStone information and releases: www.millstone.org
0035:
0036: ********************************************************************** */
0037:
0038: package org.millstone.base.ui;
0039:
0040: import java.util.Collection;
0041: import java.util.Collections;
0042: import java.util.HashSet;
0043: import java.util.HashMap;
0044: import java.util.Iterator;
0045: import java.util.LinkedList;
0046: import java.util.Map;
0047: import java.util.Set;
0048:
0049: import org.millstone.base.data.Container;
0050: import org.millstone.base.data.Item;
0051: import org.millstone.base.data.Property;
0052: import org.millstone.base.data.util.IndexedContainer;
0053: import org.millstone.base.terminal.KeyMapper;
0054: import org.millstone.base.terminal.PaintException;
0055: import org.millstone.base.terminal.PaintTarget;
0056: import org.millstone.base.terminal.Resource;
0057:
0058: /** <p>A class representing a selection of items the user has selected in a
0059: * UI. The set of choices is presented as a set of
0060: * {@link org.millstone.base.data.Item}s in a
0061: * {@link org.millstone.base.data.Container}.</p>
0062: *
0063: * <p>A <code>Select</code> component may be in single- or multiselect mode.
0064: * Multiselect mode means that more than one item can be selected
0065: * simultaneously.</p>
0066: *
0067: * @author IT Mill Ltd.
0068: * @version 3.1.1
0069: * @since 3.0
0070: */
0071: public class Select extends AbstractField implements Container,
0072: Container.Viewer, Container.PropertySetChangeListener,
0073: Container.PropertySetChangeNotifier,
0074: Container.ItemSetChangeNotifier,
0075: Container.ItemSetChangeListener {
0076:
0077: /** Item caption mode: Item's ID's <code>String</code> representation
0078: * is used as caption.
0079: */
0080: public static final int ITEM_CAPTION_MODE_ID = 0;
0081:
0082: /** Item caption mode: Item's <code>String</code> representation is
0083: * used as caption.
0084: */
0085: public static final int ITEM_CAPTION_MODE_ITEM = 1;
0086:
0087: /** Item caption mode: Index of the item is used as caption. The
0088: * index mode can only be used with the containers implementing the
0089: * {@link org.millstone.base.data.Container.Indexed} interface.
0090: */
0091: public static final int ITEM_CAPTION_MODE_INDEX = 2;
0092:
0093: /** Item caption mode: If an Item has a caption it's used, if not,
0094: * Item's ID's <code>String</code> representation is used as caption.
0095: * <b>This is the default</b>.
0096: */
0097: public static final int ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID = 3;
0098:
0099: /** Item caption mode: Captions must be explicitly specified.
0100: */
0101: public static final int ITEM_CAPTION_MODE_EXPLICIT = 4;
0102:
0103: /** Item caption mode: Only icons are shown, captions are hidden.
0104: */
0105: public static final int ITEM_CAPTION_MODE_ICON_ONLY = 5;
0106:
0107: /** Item caption mode: Item captions are read from property specified
0108: * with <code>setItemCaptionPropertyId</code>.
0109: */
0110: public static final int ITEM_CAPTION_MODE_PROPERTY = 6;
0111:
0112: /** Is the select in multiselect mode? */
0113: private boolean multiSelect = false;
0114:
0115: /** Select options */
0116: protected Container items;
0117:
0118: /** Is the user allowed to add new options? */
0119: private boolean allowNewOptions;
0120:
0121: /** Keymapper used to map key values */
0122: protected KeyMapper itemIdMapper = new KeyMapper();
0123:
0124: /** Item icons */
0125: private HashMap itemIcons = new HashMap();
0126:
0127: /** Item captions */
0128: private HashMap itemCaptions = new HashMap();
0129:
0130: /** Item caption mode */
0131: private int itemCaptionMode = ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID;
0132:
0133: /** Item caption source property id */
0134: private Object itemCaptionPropertyId = null;
0135:
0136: /** Item icon source property id */
0137: private Object itemIconPropertyId = null;
0138:
0139: /** List of property set change event listeners */
0140: private LinkedList propertySetEventListeners = null;
0141:
0142: /** List of item set change event listeners */
0143: private LinkedList itemSetEventListeners = null;
0144:
0145: /** Item id that represents null selection of this select.
0146: *
0147: * <p>Data interface does not support nulls as item ids. Selecting the item idetified
0148: * by this id is the same as selecting no items at all. This setting only affects the
0149: * single select mode.</p>
0150: */
0151: private Object nullSelectionItemId = null;
0152:
0153: /* Constructors ********************************************************* */
0154:
0155: /** Creates an empty Select.
0156: * The caption is not used.
0157: */
0158: public Select() {
0159: setContainerDataSource(new IndexedContainer());
0160: }
0161:
0162: /** Creates an empty Select with caption.
0163: */
0164: public Select(String caption) {
0165: setContainerDataSource(new IndexedContainer());
0166: setCaption(caption);
0167: }
0168:
0169: /** Creates a new select wthat is connected to a data-source.
0170: * @param dataSource Container datasource to be selected from by this select.
0171: * @param caption Caption of the component.
0172: * @param selected Selected item id or null, if none selected.
0173: */
0174: public Select(String caption, Container dataSource) {
0175: setCaption(caption);
0176: setContainerDataSource(dataSource);
0177: }
0178:
0179: /** Creates a new select that is filled from a collection of option values.
0180: * @param caption Caption of this field.
0181: * @param options Collection containing the options.
0182: * @param selected Selected option or null, if none selected.
0183: */
0184: public Select(String caption, Collection options) {
0185:
0186: // Create options container and add given options to it
0187: Container c = new IndexedContainer();
0188: if (options != null)
0189: for (Iterator i = options.iterator(); i.hasNext();)
0190: c.addItem(i.next());
0191:
0192: setCaption(caption);
0193: setContainerDataSource((Container) c);
0194: }
0195:
0196: /* Component methods **************************************************** */
0197:
0198: /** Paint the content of this component.
0199: * @param event PaintEvent.
0200: * @throws PaintException The paint operation failed.
0201: */
0202: public void paintContent(PaintTarget target) throws PaintException {
0203:
0204: // Paint field properties
0205: super .paintContent(target);
0206:
0207: // Paint select attributes
0208: if (isMultiSelect())
0209: target.addAttribute("selectmode", "multi");
0210: if (isNewItemsAllowed())
0211: target.addAttribute("allownewitem", true);
0212:
0213: // Paint options and create array of selected id keys
0214: String[] selectedKeys;
0215: if (isMultiSelect())
0216: selectedKeys = new String[((Set) getValue()).size()];
0217: else
0218: selectedKeys = new String[(getValue() == null
0219: && getNullSelectionItemId() == null ? 0 : 1)];
0220: int keyIndex = 0;
0221: target.startTag("options");
0222:
0223: // Support for external null selection item id
0224: Collection ids = getItemIds();
0225: if (getNullSelectionItemId() != null
0226: && (!ids.contains(getNullSelectionItemId()))) {
0227:
0228: // Get the option attribute values
0229: Object id = getNullSelectionItemId();
0230: String key = itemIdMapper.key(id);
0231: String caption = getItemCaption(id);
0232: Resource icon = getItemIcon(id);
0233:
0234: // Paint option
0235: target.startTag("so");
0236: if (icon != null)
0237: target.addAttribute("icon", icon);
0238: target.addAttribute("caption", caption);
0239: target.addAttribute("nullselection", true);
0240: target.addAttribute("key", key);
0241: if (isSelected(id)) {
0242: target.addAttribute("selected", true);
0243: selectedKeys[keyIndex++] = key;
0244: }
0245: target.endTag("so");
0246: }
0247:
0248: // Paint available selection options from data source
0249: for (Iterator i = getItemIds().iterator(); i.hasNext();) {
0250:
0251: // Get the option attribute values
0252: Object id = i.next();
0253: String key = itemIdMapper.key(id);
0254: String caption = getItemCaption(id);
0255: Resource icon = getItemIcon(id);
0256:
0257: // Paint option
0258: target.startTag("so");
0259: if (icon != null)
0260: target.addAttribute("icon", icon);
0261: target.addAttribute("caption", caption);
0262: if (id != null && id.equals(getNullSelectionItemId()))
0263: target.addAttribute("nullselection", true);
0264: target.addAttribute("key", key);
0265: if (isSelected(id) && keyIndex < selectedKeys.length) {
0266: target.addAttribute("selected", true);
0267: selectedKeys[keyIndex++] = key;
0268: }
0269: target.endTag("so");
0270: }
0271: target.endTag("options");
0272:
0273: // Paint variables
0274: target.addVariable(this , "selected", selectedKeys);
0275: if (isNewItemsAllowed())
0276: target.addVariable(this , "newitem", "");
0277: }
0278:
0279: /** Invoked when the value of a variable has changed.
0280: * @param event Variable change event containing the information about
0281: * the changed variable.
0282: */
0283: public void changeVariables(Object source, Map variables) {
0284:
0285: // Try to set the property value
0286:
0287: // New option entered (and it is allowed)
0288: String newitem = (String) variables.get("newitem");
0289: if (newitem != null && newitem.length() > 0) {
0290:
0291: // Check for readonly
0292: if (isReadOnly())
0293: throw new Property.ReadOnlyException();
0294:
0295: // Add new option
0296: if (addItem(newitem) != null) {
0297:
0298: // Set the caption property, if used
0299: if (getItemCaptionPropertyId() != null)
0300: try {
0301: getContainerProperty(newitem,
0302: getItemCaptionPropertyId()).setValue(
0303: newitem);
0304: } catch (Property.ConversionException ignored) {
0305: // The conversion exception is safely ignored, the caption is
0306: // just missing
0307: }
0308: }
0309: }
0310:
0311: // Selection change
0312: if (variables.containsKey("selected")) {
0313: String[] ka = (String[]) variables.get("selected");
0314:
0315: // Multiselect mode
0316: if (isMultiSelect()) {
0317:
0318: // Convert the key-array to id-set
0319: LinkedList s = new LinkedList();
0320: for (int i = 0; i < ka.length; i++) {
0321: Object id = itemIdMapper.get(ka[i]);
0322: if (id != null && containsId(id))
0323: s.add(id);
0324: else if (itemIdMapper.isNewIdKey(ka[i])
0325: && newitem != null && newitem.length() > 0)
0326: s.add(newitem);
0327: }
0328:
0329: // Limit the deselection to the set of visible items
0330: // (non-visible items can not be deselected)
0331: Collection visible = getVisibleItemIds();
0332: if (visible != null) {
0333: Set newsel = (Set) getValue();
0334: if (newsel == null)
0335: newsel = new HashSet();
0336: else
0337: newsel = new HashSet(newsel);
0338: newsel.removeAll(visible);
0339: newsel.addAll(s);
0340: super .setValue(newsel);
0341: }
0342: }
0343:
0344: // Single select mode
0345: else {
0346: if (ka.length == 0) {
0347:
0348: // Allow deselection only if the deselected item is visible
0349: Object current = getValue();
0350: Collection visible = getVisibleItemIds();
0351: if (visible != null && visible.contains(current))
0352: setValue(null);
0353: } else {
0354: Object id = itemIdMapper.get(ka[0]);
0355: if (id != null
0356: && id.equals(getNullSelectionItemId()))
0357: setValue(null);
0358: else if (itemIdMapper.isNewIdKey(ka[0]))
0359: setValue(newitem);
0360: else
0361: setValue(id);
0362: }
0363: }
0364: }
0365: }
0366:
0367: /** Get component UIDL tag.
0368: * @return Component UIDL tag as string.
0369: */
0370: public String getTag() {
0371: return "select";
0372: }
0373:
0374: /** Get the visible item ids. In Select, this returns list of all item ids,
0375: * but can be overriden in subclasses if they paint only part of the items
0376: * to the terminal or null if no items is visible.
0377: */
0378: public Collection getVisibleItemIds() {
0379: if (isVisible())
0380: return getItemIds();
0381: return null;
0382: }
0383:
0384: /* Property methods ***************************************************** */
0385:
0386: /** Return the type of the property.
0387: * getValue and setValue functions must be compatible with this type:
0388: * one can safely cast getValue() to given type and pass any variable
0389: * assignable to this type as a parameter to setValue().
0390: * @return type Type of the property.
0391: */
0392: public Class getType() {
0393: if (isMultiSelect())
0394: return Set.class;
0395: else
0396: return Object.class;
0397: }
0398:
0399: /** Get the selected item id or in multiselect mode a set of selected ids.
0400: */
0401: public Object getValue() {
0402: Object retValue = super .getValue();
0403:
0404: if (isMultiSelect()) {
0405:
0406: // If the return value is not a set
0407: if (retValue == null)
0408: return new HashSet();
0409: if (retValue instanceof Set) {
0410: return Collections.unmodifiableSet((Set) retValue);
0411: } else if (retValue instanceof Collection) {
0412: return new HashSet((Collection) retValue);
0413: } else {
0414: Set s = new HashSet();
0415: if (items.containsId(retValue))
0416: s.add(retValue);
0417: return s;
0418: }
0419:
0420: } else
0421: return retValue;
0422: }
0423:
0424: /** Set the visible value of the property.
0425: *
0426: * <p>The value of the select is the selected item id. If the select is in
0427: * multiselect-mode, the value is a set of selected item keys. In multiselect
0428: * mode all collections of id:s can be assigned.</p>
0429: *
0430: * @param newValue New selected item or collection of selected items.
0431: */
0432: public void setValue(Object newValue)
0433: throws Property.ReadOnlyException,
0434: Property.ConversionException {
0435:
0436: if (isMultiSelect()) {
0437: if (newValue == null)
0438: super .setValue(new HashSet());
0439: else if (Collection.class.isAssignableFrom(newValue
0440: .getClass()))
0441: super .setValue(new HashSet((Collection) newValue));
0442: } else if (newValue == null || items.containsId(newValue))
0443: super .setValue(newValue);
0444: }
0445:
0446: /* Container methods **************************************************** */
0447:
0448: /** Get the item from the container with given id.
0449: * If the container does not contain the requested item, null is returned.
0450: */
0451: public Item getItem(Object itemId) {
0452: return items.getItem(itemId);
0453: }
0454:
0455: /** Get item Id collection from the container.
0456: * @return Collection of item ids.
0457: */
0458: public Collection getItemIds() {
0459: return items.getItemIds();
0460: }
0461:
0462: /** Get property Id collection from the container.
0463: * @return Collection of property ids.
0464: */
0465: public Collection getContainerPropertyIds() {
0466: return items.getContainerPropertyIds();
0467: }
0468:
0469: /** Get property type.
0470: * @param id Id identifying the of the property.
0471: */
0472: public Class getType(Object propertyId) {
0473: return items.getType(propertyId);
0474: }
0475:
0476: /** Get the number of items in the container.
0477: * @return Number of items in the container.
0478: */
0479: public int size() {
0480: return items.size();
0481: }
0482:
0483: /** Test, if the collection contains an item with given id.
0484: * @param itemId Id the of item to be tested.
0485: */
0486: public boolean containsId(Object itemId) {
0487: if (itemId != null)
0488: return items.containsId(itemId);
0489: else
0490: return false;
0491: }
0492:
0493: /**
0494: * @see org.millstone.base.data.Container#getContainerProperty(Object, Object)
0495: */
0496: public Property getContainerProperty(Object itemId,
0497: Object propertyId) {
0498: return items.getContainerProperty(itemId, propertyId);
0499: }
0500:
0501: /* Container.Managed methods ******************************************** */
0502:
0503: /** Add new property to all items.
0504: * Adds a property with given id, type and default value to all items
0505: * in the container.
0506: *
0507: * This functionality is optional. If the function is unsupported, it always
0508: * returns false.
0509: *
0510: * @return True iff the operation succeeded.
0511: */
0512: public boolean addContainerProperty(Object propertyId, Class type,
0513: Object defaultValue) throws UnsupportedOperationException {
0514:
0515: boolean retval = items.addContainerProperty(propertyId, type,
0516: defaultValue);
0517: if (retval
0518: && !(items instanceof Container.PropertySetChangeNotifier)) {
0519: firePropertySetChange();
0520: }
0521: return retval;
0522: }
0523:
0524: /** Remove all items from the container.
0525: *
0526: * This functionality is optional. If the function is unsupported, it always
0527: * returns false.
0528: *
0529: * @return True iff the operation succeeded.
0530: */
0531: public boolean removeAllItems()
0532: throws UnsupportedOperationException {
0533:
0534: boolean retval = items.removeAllItems();
0535: this .itemIdMapper.removeAll();
0536: if (retval) {
0537: setValue(null);
0538: if (!(items instanceof Container.ItemSetChangeNotifier))
0539: fireItemSetChange();
0540: }
0541: return retval;
0542: }
0543:
0544: /** Create a new item into container with container managed id.
0545: * The id of the created new item is returned. The item can be fetched with
0546: * getItem() method.
0547: * if the creation fails, null is returned.
0548: *
0549: * @return Id of the created item or null in case of failure.
0550: */
0551: public Object addItem() throws UnsupportedOperationException {
0552:
0553: Object retval = items.addItem();
0554: if (retval != null
0555: && !(items instanceof Container.ItemSetChangeNotifier))
0556: fireItemSetChange();
0557: return retval;
0558: }
0559:
0560: /** Create a new item into container.
0561: * The created new item is returned and ready for setting property values.
0562: * if the creation fails, null is returned. In case the container already
0563: * contains the item, null is returned.
0564: *
0565: * This functionality is optional. If the function is unsupported, it always
0566: * returns null.
0567: *
0568: * @param itemId Identification of the item to be created.
0569: * @return Created item with the given id, or null in case of failure.
0570: */
0571: public Item addItem(Object itemId)
0572: throws UnsupportedOperationException {
0573:
0574: Item retval = items.addItem(itemId);
0575: if (retval != null
0576: && !(items instanceof Container.ItemSetChangeNotifier))
0577: fireItemSetChange();
0578: return retval;
0579: }
0580:
0581: /** Remove item identified by Id from the container.
0582: * This functionality is optional. If the function is not implemented,
0583: * the functions allways returns false.
0584: *
0585: * @return True iff the operation succeeded.
0586: */
0587: public boolean removeItem(Object itemId)
0588: throws UnsupportedOperationException {
0589:
0590: unselect(itemId);
0591: boolean retval = items.removeItem(itemId);
0592: this .itemIdMapper.remove(itemId);
0593: if (retval
0594: && !(items instanceof Container.ItemSetChangeNotifier))
0595: fireItemSetChange();
0596: return retval;
0597: }
0598:
0599: /** Remove property from all items.
0600: * Removes a property with given id from all the items in the container.
0601: *
0602: * This functionality is optional. If the function is unsupported, it always
0603: * returns false.
0604: *
0605: * @return True iff the operation succeeded.
0606: */
0607: public boolean removeContainerProperty(Object propertyId)
0608: throws UnsupportedOperationException {
0609:
0610: boolean retval = items.removeContainerProperty(propertyId);
0611: if (retval
0612: && !(items instanceof Container.PropertySetChangeNotifier))
0613: firePropertySetChange();
0614: return retval;
0615: }
0616:
0617: /* Container.Viewer methods ********************************************* */
0618:
0619: /** Set the container as data-source for viewing. */
0620: public void setContainerDataSource(Container newDataSource) {
0621: if (newDataSource == null)
0622: newDataSource = new IndexedContainer();
0623:
0624: if (items != newDataSource) {
0625:
0626: // Remove listeners from the old datasource
0627: if (items != null) {
0628: try {
0629: ((Container.ItemSetChangeNotifier) items)
0630: .removeListener((Container.ItemSetChangeListener) this );
0631: } catch (ClassCastException ignored) {
0632: // Ignored
0633: }
0634: try {
0635: ((Container.PropertySetChangeNotifier) items)
0636: .removeListener((Container.PropertySetChangeListener) this );
0637: } catch (ClassCastException ignored) {
0638: // Ignored
0639: }
0640: }
0641:
0642: // Assign new data source
0643: items = newDataSource;
0644:
0645: // Clear itemIdMapper also
0646: this .itemIdMapper.removeAll();
0647:
0648: // Add listeners
0649: if (items != null) {
0650: try {
0651: ((Container.ItemSetChangeNotifier) items)
0652: .addListener((Container.ItemSetChangeListener) this );
0653: } catch (ClassCastException ignored) {
0654: // Ignored
0655: }
0656: try {
0657: ((Container.PropertySetChangeNotifier) items)
0658: .addListener((Container.PropertySetChangeListener) this );
0659: } catch (ClassCastException ignored) {
0660: // Ignored
0661: }
0662: }
0663: //TODO: This should be conditional
0664: fireValueChange();
0665: }
0666: }
0667:
0668: /** Get viewing data-source container. */
0669: public Container getContainerDataSource() {
0670: return items;
0671: }
0672:
0673: /* Select attributes **************************************************** */
0674:
0675: /** Is the select in multiselect mode? In multiselect mode
0676: * @return Value of property multiSelect.
0677: */
0678: public boolean isMultiSelect() {
0679: return this .multiSelect;
0680: }
0681:
0682: /** Set the multiselect mode.
0683: * Setting multiselect mode false may loose selection information: if
0684: * selected items set contains one or more selected items, only one of the
0685: * selected items is kept as selected.
0686: *
0687: * @param multiSelect New value of property multiSelect.
0688: */
0689: public void setMultiSelect(boolean multiSelect) {
0690:
0691: if (multiSelect != this .multiSelect) {
0692:
0693: // Selection before mode change
0694: Object oldValue = getValue();
0695:
0696: this .multiSelect = multiSelect;
0697:
0698: // Convert the value type
0699: if (multiSelect) {
0700: Set s = new HashSet();
0701: if (oldValue != null)
0702: s.add(oldValue);
0703: setValue(s);
0704: } else {
0705: Set s = (Set) oldValue;
0706: if (s == null || s.isEmpty())
0707: setValue(null);
0708: else
0709:
0710: // Set the single select to contain only the first
0711: // selected value in the multiselect
0712: setValue(s.iterator().next());
0713: }
0714:
0715: requestRepaint();
0716: }
0717: }
0718:
0719: /** Does the select allow adding new options by the user.
0720: * If true, the new options can be added to the Container. The text entered
0721: * by the user is used as id. No that data-source must allow adding new
0722: * items (it must implement Container.Managed).
0723: * @return True iff additions are allowed.
0724: */
0725: public boolean isNewItemsAllowed() {
0726:
0727: return this .allowNewOptions;
0728: }
0729:
0730: /** Enable or disable possibility to add new options by the user.
0731: * @param allowNewOptions New value of property allowNewOptions.
0732: */
0733: public void setNewItemsAllowed(boolean allowNewOptions) {
0734:
0735: // Only handle change requests
0736: if (this .allowNewOptions != allowNewOptions) {
0737:
0738: this .allowNewOptions = allowNewOptions;
0739:
0740: requestRepaint();
0741: }
0742: }
0743:
0744: /** Override the caption of an item.
0745: * Setting caption explicitly overrides id, item and index captions.
0746: *
0747: * @param itemId The id of the item to be recaptioned.
0748: * @param caption New caption.
0749: */
0750: public void setItemCaption(Object itemId, String caption) {
0751: if (itemId != null) {
0752: itemCaptions.put(itemId, caption);
0753: requestRepaint();
0754: }
0755: }
0756:
0757: /** Get the caption of an item.
0758: * The caption is generated as specified by the item caption mode. See
0759: * <code>setItemCaptionMode()</code> for more details.
0760: *
0761: * @param itemId The id of the item to be queried.
0762: * @return caption for specified item.
0763: */
0764: public String getItemCaption(Object itemId) {
0765:
0766: // Null items can not be found
0767: if (itemId == null)
0768: return null;
0769:
0770: String caption = null;
0771:
0772: switch (getItemCaptionMode()) {
0773:
0774: case ITEM_CAPTION_MODE_ID:
0775: caption = itemId.toString();
0776: break;
0777:
0778: case ITEM_CAPTION_MODE_INDEX:
0779: try {
0780: caption = String.valueOf(((Container.Indexed) items)
0781: .indexOfId(itemId));
0782: } catch (ClassCastException ignored) {
0783: }
0784: break;
0785:
0786: case ITEM_CAPTION_MODE_ITEM:
0787: Item i = getItem(itemId);
0788: if (i != null)
0789: caption = i.toString();
0790: break;
0791:
0792: case ITEM_CAPTION_MODE_EXPLICIT:
0793: caption = (String) itemCaptions.get(itemId);
0794: break;
0795:
0796: case ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID:
0797: caption = (String) itemCaptions.get(itemId);
0798: if (caption == null)
0799: caption = itemId.toString();
0800: break;
0801:
0802: case ITEM_CAPTION_MODE_PROPERTY:
0803: Property p = getContainerProperty(itemId,
0804: getItemCaptionPropertyId());
0805: if (p != null)
0806: caption = p.toString();
0807: break;
0808: }
0809:
0810: // All items must have some captions
0811: return caption != null ? caption : "";
0812: }
0813:
0814: /** Set icon for an item.
0815: *
0816: * @param itemId The id of the item to be assigned an icon.
0817: * @param icon New icon.
0818: */
0819: public void setItemIcon(Object itemId, Resource icon) {
0820: if (itemId != null) {
0821: if (icon == null)
0822: itemIcons.remove(itemId);
0823: else
0824: itemIcons.put(itemId, icon);
0825: requestRepaint();
0826: }
0827: }
0828:
0829: /** Get the item icon.
0830: *
0831: * @param itemId The id of the item to be assigned an icon.
0832: * @return Icon for the item or null, if not specified.
0833: */
0834: public Resource getItemIcon(Object itemId) {
0835: Resource explicit = (Resource) itemIcons.get(itemId);
0836: if (explicit != null)
0837: return explicit;
0838:
0839: if (getItemIconPropertyId() == null)
0840: return null;
0841:
0842: Property ip = getContainerProperty(itemId,
0843: getItemIconPropertyId());
0844: if (ip == null)
0845: return null;
0846: Object icon = ip.getValue();
0847: if (icon instanceof Resource)
0848: return (Resource) icon;
0849:
0850: return null;
0851: }
0852:
0853: /** Set the item caption mode.
0854: *
0855: * <p>The mode can be one of the following ones:
0856: * <ul>
0857: * <li><code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> : Items
0858: * Id-objects <code>toString()</code> is used as item caption. If
0859: * caption is explicitly specified, it overrides the id-caption.
0860: * <li><code>ITEM_CAPTION_MODE_ID</code> : Items Id-objects
0861: * <code>toString()</code> is used as item caption.</li>
0862: * <li><code>ITEM_CAPTION_MODE_ITEM</code> : Item-objects
0863: * <code>toString()</code> is used as item caption.</li>
0864: * <li><code>ITEM_CAPTION_MODE_INDEX</code> : The index of the item is
0865: * used as item caption. The index mode can
0866: * only be used with the containers implementing
0867: * <code>Container.Indexed</code> interface.</li>
0868: * <li><code>ITEM_CAPTION_MODE_EXPLICIT</code> : The item captions
0869: * must be explicitly specified.</li>
0870: * <li><code>ITEM_CAPTION_MODE_PROPERTY</code> : The item captions
0871: * are read from property, that must be specified with
0872: * <code>setItemCaptionPropertyId()</code>.</li>
0873: * </ul>
0874: * The <code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> is the default
0875: * mode.
0876: * </p>
0877: *
0878: * @param mode One of the modes listed above.
0879: */
0880: public void setItemCaptionMode(int mode) {
0881: if (ITEM_CAPTION_MODE_ID <= mode
0882: && mode <= ITEM_CAPTION_MODE_PROPERTY) {
0883: itemCaptionMode = mode;
0884: requestRepaint();
0885: }
0886: }
0887:
0888: /** Get the item caption mode.
0889: *
0890: * <p>The mode can be one of the following ones:
0891: * <ul>
0892: * <li><code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> : Items
0893: * Id-objects <code>toString()</code> is used as item caption. If
0894: * caption is explicitly specified, it overrides the id-caption.
0895: * <li><code>ITEM_CAPTION_MODE_ID</code> : Items Id-objects
0896: * <code>toString()</code> is used as item caption.</li>
0897: * <li><code>ITEM_CAPTION_MODE_ITEM</code> : Item-objects
0898: * <code>toString()</code> is used as item caption.</li>
0899: * <li><code>ITEM_CAPTION_MODE_INDEX</code> : The index of the item is
0900: * used as item caption. The index mode can
0901: * only be used with the containers implementing
0902: * <code>Container.Indexed</code> interface.</li>
0903: * <li><code>ITEM_CAPTION_MODE_EXPLICIT</code> : The item captions
0904: * must be explicitly specified.</li>
0905: * <li><code>ITEM_CAPTION_MODE_PROPERTY</code> : The item captions
0906: * are read from property, that must be specified with
0907: * <code>setItemCaptionPropertyId()</code>.</li>
0908: * </ul>
0909: * The <code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> is the default
0910: * mode.
0911: * </p>
0912: *
0913: * @return One of the modes listed above.
0914: */
0915: public int getItemCaptionMode() {
0916: return itemCaptionMode;
0917: }
0918:
0919: /** Set the item caption property.
0920: *
0921: * <p>Setting the id to a existing property implicitly sets
0922: * the item caption mode to <code>ITEM_CAPTION_MODE_PROPERTY</code>.
0923: * If the object is in <code>ITEM_CAPTION_MODE_PROPERTY</code>
0924: * mode, setting caption property id null resets the
0925: * item caption mode to <code>ITEM_CAPTION_EXPLICIT_DEFAULTS_ID</code>.</p>
0926:
0927: * <p>Setting the property id to null disables this feature. The
0928: * id is null by default</p>.
0929: *
0930: */
0931: public void setItemCaptionPropertyId(Object propertyId) {
0932: if (propertyId != null) {
0933: itemCaptionPropertyId = propertyId;
0934: setItemCaptionMode(ITEM_CAPTION_MODE_PROPERTY);
0935: requestRepaint();
0936: } else {
0937: itemCaptionPropertyId = null;
0938: if (getItemCaptionMode() == ITEM_CAPTION_MODE_PROPERTY)
0939: setItemCaptionMode(ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID);
0940: requestRepaint();
0941: }
0942: }
0943:
0944: /** Get the item caption property.
0945: *
0946: * @return Id of the property used as item caption source.
0947: */
0948: public Object getItemCaptionPropertyId() {
0949: return itemCaptionPropertyId;
0950: }
0951:
0952: /** Set the item icon property.
0953: *
0954: * <p>If the property id is set to a valid value, each item is given
0955: * an icon got from the given property of the items. The type
0956: * of the property must be assignable to Icon.</p>
0957: *
0958: * <p>Note that the icons set with <code>setItemIcon</code>
0959: * function override the icons from the property.</p>
0960: *
0961: * <p>Setting the property id to null disables this feature. The
0962: * id is null by default</p>.
0963: *
0964: * @param propertyId Id of the property that specifies icons for
0965: * items.
0966: */
0967: public void setItemIconPropertyId(Object propertyId) {
0968: if ((propertyId != null)
0969: && Resource.class.isAssignableFrom(getType(propertyId))) {
0970: itemIconPropertyId = propertyId;
0971: requestRepaint();
0972: } else
0973: itemIconPropertyId = null;
0974: }
0975:
0976: /** Get the item icon property.
0977: *
0978: * <p>If the property id is set to a valid value, each item is given
0979: * an icon got from the given property of the items. The type
0980: * of the property must be assignable to Icon.</p>
0981: *
0982: * <p>Note that the icons set with <code>setItemIcon</code>
0983: * function override the icons from the property.</p>
0984: *
0985: * <p>Setting the property id to null disables this feature. The
0986: * id is null by default</p>.
0987: *
0988: * @return Id of the property containing the item icons.
0989: */
0990: public Object getItemIconPropertyId() {
0991: return itemIconPropertyId;
0992: }
0993:
0994: /** Test if an item is selected
0995: *
0996: * <p>In single select mode testing selection status of the item identified
0997: * by {@link #getNullSelectionItemId()} returns true if the value of the
0998: * property is null.</p>
0999: *
1000: * @see #getNullSelectionItemId()
1001: * @see #setNullSelectionItemId(Object)
1002: * @param itemId Id the of the item to be tested
1003: */
1004: public boolean isSelected(Object itemId) {
1005: if (itemId == null)
1006: return false;
1007: if (isMultiSelect())
1008: return ((Set) getValue()).contains(itemId);
1009: else {
1010: Object value = getValue();
1011: return itemId
1012: .equals(value == null ? getNullSelectionItemId()
1013: : value);
1014: }
1015: }
1016:
1017: /** Select an item.
1018: *
1019: * <p>In single select mode selecting item identified
1020: * by {@link #getNullSelectionItemId()} sets the value of the
1021: * property to null.</p>
1022: *
1023: * @see #getNullSelectionItemId()
1024: * @see #setNullSelectionItemId(Object)
1025: * @param itemId Item to be selected.
1026: */
1027: public void select(Object itemId) {
1028: if (!isSelected(itemId) && items.containsId(itemId)) {
1029: if (isMultiSelect()) {
1030: Set s = new HashSet((Set) getValue());
1031: s.add(itemId);
1032: setValue(s);
1033: } else if (itemId.equals(getNullSelectionItemId()))
1034: setValue(null);
1035: else
1036: setValue(itemId);
1037: }
1038: }
1039:
1040: /** Unselect an item.
1041: *
1042: * @see #getNullSelectionItemId()
1043: * @see #setNullSelectionItemId(Object)
1044: * @param itemId Item to be unselected.
1045: */
1046: public void unselect(Object itemId) {
1047: if (isSelected(itemId)) {
1048: if (isMultiSelect()) {
1049: Set s = new HashSet((Set) getValue());
1050: s.remove(itemId);
1051: setValue(s);
1052: } else
1053: setValue(null);
1054: }
1055: }
1056:
1057: /**
1058: * @see org.millstone.base.data.Container.PropertySetChangeListener#containerPropertySetChange(org.millstone.base.data.Container.PropertySetChangeEvent)
1059: */
1060: public void containerPropertySetChange(
1061: Container.PropertySetChangeEvent event) {
1062: firePropertySetChange();
1063: }
1064:
1065: /**
1066: * @see org.millstone.base.data.Container.PropertySetChangeNotifier#addListener(org.millstone.base.data.Container.PropertySetChangeListener)
1067: */
1068: public void addListener(Container.PropertySetChangeListener listener) {
1069: if (propertySetEventListeners == null)
1070: propertySetEventListeners = new LinkedList();
1071: propertySetEventListeners.add(listener);
1072: }
1073:
1074: /**
1075: * @see org.millstone.base.data.Container.PropertySetChangeNotifier#removeListener(org.millstone.base.data.Container.PropertySetChangeListener)
1076: */
1077: public void removeListener(
1078: Container.PropertySetChangeListener listener) {
1079: if (propertySetEventListeners != null) {
1080: propertySetEventListeners.remove(listener);
1081: if (propertySetEventListeners.isEmpty())
1082: propertySetEventListeners = null;
1083: }
1084: }
1085:
1086: /**
1087: * @see org.millstone.base.data.Container.ItemSetChangeNotifier#addListener(org.millstone.base.data.Container.ItemSetChangeListener)
1088: */
1089: public void addListener(Container.ItemSetChangeListener listener) {
1090: if (itemSetEventListeners == null)
1091: itemSetEventListeners = new LinkedList();
1092: itemSetEventListeners.add(listener);
1093: }
1094:
1095: /**
1096: * @see org.millstone.base.data.Container.ItemSetChangeNotifier#removeListener(org.millstone.base.data.Container.ItemSetChangeListener)
1097: */
1098: public void removeListener(Container.ItemSetChangeListener listener) {
1099: if (itemSetEventListeners != null) {
1100: itemSetEventListeners.remove(listener);
1101: if (itemSetEventListeners.isEmpty())
1102: itemSetEventListeners = null;
1103: }
1104: }
1105:
1106: /**
1107: * @see org.millstone.base.data.Container.ItemSetChangeListener#containerItemSetChange(org.millstone.base.data.Container.ItemSetChangeEvent)
1108: */
1109: public void containerItemSetChange(
1110: Container.ItemSetChangeEvent event) {
1111: // Clear item id mapping table
1112: this .itemIdMapper.removeAll();
1113:
1114: // Notify all listeners
1115: fireItemSetChange();
1116: }
1117:
1118: /** Fire property set change event */
1119: protected void firePropertySetChange() {
1120: if (propertySetEventListeners != null
1121: && !propertySetEventListeners.isEmpty()) {
1122: Container.PropertySetChangeEvent event = new PropertySetChangeEvent();
1123: Object[] listeners = propertySetEventListeners.toArray();
1124: for (int i = 0; i < listeners.length; i++)
1125: ((Container.PropertySetChangeListener) listeners[i])
1126: .containerPropertySetChange(event);
1127: }
1128: requestRepaint();
1129: }
1130:
1131: /** Fire item set change event */
1132: protected void fireItemSetChange() {
1133: if (itemSetEventListeners != null
1134: && !itemSetEventListeners.isEmpty()) {
1135: Container.ItemSetChangeEvent event = new ItemSetChangeEvent();
1136: Object[] listeners = itemSetEventListeners.toArray();
1137: for (int i = 0; i < listeners.length; i++)
1138: ((Container.ItemSetChangeListener) listeners[i])
1139: .containerItemSetChange(event);
1140: }
1141: requestRepaint();
1142: }
1143:
1144: /** Implementation of item set change event */
1145: private class ItemSetChangeEvent implements
1146: Container.ItemSetChangeEvent {
1147:
1148: /**
1149: * @see org.millstone.base.data.Container.ItemSetChangeEvent#getContainer()
1150: */
1151: public Container getContainer() {
1152: return Select.this ;
1153: }
1154:
1155: }
1156:
1157: /** Implementation of property set change event */
1158: private class PropertySetChangeEvent implements
1159: Container.PropertySetChangeEvent {
1160:
1161: /**
1162: * @see org.millstone.base.data.Container.PropertySetChangeEvent#getContainer()
1163: */
1164: public Container getContainer() {
1165: return Select.this ;
1166: }
1167:
1168: }
1169:
1170: /** Returns the item id that represents null value of this select in single select mode.
1171: *
1172: * <p>Data interface does not support nulls as item ids. Selecting the item idetified
1173: * by this id is the same as selecting no items at all. This setting only affects the
1174: * single select mode.</p>
1175:
1176: * @return Object Null value item id.
1177: * @see #setNullSelectionItemId(Object)
1178: * @see #isSelected(Object)
1179: * @see #select(Object)
1180: */
1181: public final Object getNullSelectionItemId() {
1182: return nullSelectionItemId;
1183: }
1184:
1185: /** Sets the item id that represents null value of this select.
1186: *
1187: * <p>Data interface does not support nulls as item ids. Selecting the item idetified
1188: * by this id is the same as selecting no items at all. This setting only affects the
1189: * single select mode.</p>
1190: *
1191: * @param nullPropertyValueContainerItemId The nullPropertyValueContainerItemId to set
1192: * @see #getNullSelectionItemId()
1193: * @see #isSelected(Object)
1194: * @see #select(Object)
1195: */
1196: public void setNullSelectionItemId(Object nullSelectionItemId) {
1197: this.nullSelectionItemId = nullSelectionItemId;
1198: }
1199: }
|