0001: /*******************************************************************************
0002: *
0003: * Millstone(TM) Open Sourced User Interface Library for Internet Development
0004: * with Java
0005: *
0006: * Millstone is a registered trademark of IT Mill Ltd Copyright (C)
0007: * 2000-2005 IT Mill Ltd
0008: *
0009: * ************************************************************************
0010: *
0011: * This library is free software; you can redistribute it and/or
0012: * modify it under the terms of the GNU Lesser General Public
0013: * license version 2.1 as published by the Free Software Foundation.
0014: *
0015: * This library is distributed in the hope that it will be useful, but WITHOUT
0016: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
0017: * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
0018: * details.
0019: *
0020: * You should have received a copy of the GNU Lesser General Public License
0021: * along with this library; if not, write to the Free Software Foundation, Inc.,
0022: * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0023: *
0024: * ************************************************************************
0025: *
0026: * For more information, contact:
0027: *
0028: * IT Mill Ltd phone: +358 2 4802 7180 Ruukinkatu 2-4 fax: +358 2 4802 7181
0029: * 20540, Turku email: info@itmill.com Finland company www: www.itmill.com
0030: *
0031: * Primary source for MillStone information and releases: www.millstone.org
0032: * */package org.millstone.base.ui;
0033:
0034: import java.util.Collection;
0035: import java.util.HashMap;
0036: import java.util.HashSet;
0037: import java.util.Iterator;
0038: import java.util.LinkedHashSet;
0039: import java.util.LinkedList;
0040: import java.util.Map;
0041: import java.util.Set;
0042: import java.util.StringTokenizer;
0043:
0044: import org.millstone.base.data.Container;
0045: import org.millstone.base.data.Item;
0046: import org.millstone.base.data.Property;
0047: import org.millstone.base.data.util.ContainerOrderedWrapper;
0048: import org.millstone.base.data.util.IndexedContainer;
0049: import org.millstone.base.event.Action;
0050: import org.millstone.base.terminal.KeyMapper;
0051: import org.millstone.base.terminal.PaintException;
0052: import org.millstone.base.terminal.PaintTarget;
0053: import org.millstone.base.terminal.Resource;
0054:
0055: /**
0056: * Table component is used for representing data or components in pageable and
0057: * selectable table.
0058: *
0059: * @author IT Mill Ltd.
0060: * @version 3.1.1 @since 3.0
0061: */
0062: public class Table extends Select implements Action.Container,
0063: Container.Ordered, Container.Sortable {
0064:
0065: private static final int CELL_KEY = 0;
0066:
0067: private static final int CELL_HEADER = 1;
0068:
0069: private static final int CELL_ICON = 2;
0070:
0071: private static final int CELL_ITEMID = 3;
0072:
0073: private static final int CELL_FIRSTCOL = 4;
0074:
0075: /**
0076: * Left column alignment. <b>This is the default behaviour. </b>
0077: */
0078: public static final String ALIGN_LEFT = "b";
0079:
0080: /** Center column alignment. */
0081: public static final String ALIGN_CENTER = "c";
0082:
0083: /** Right column alignment. */
0084: public static final String ALIGN_RIGHT = "e";
0085:
0086: /**
0087: * Column header mode: Column headers are hidden. <b>This is the default
0088: * behaviour. </b>
0089: */
0090: public static final int COLUMN_HEADER_MODE_HIDDEN = -1;
0091:
0092: /**
0093: * Column header mode: Property ID:s are used as column headers.
0094: */
0095: public static final int COLUMN_HEADER_MODE_ID = 0;
0096:
0097: /**
0098: * Column header mode: Column headers are explicitly specified with
0099: * <code>setColumnHeaders()</code>
0100: */
0101: public static final int COLUMN_HEADER_MODE_EXPLICIT = 1;
0102:
0103: /**
0104: * Column header mode: Column headers are explicitly specified with
0105: * <code>setColumnHeaders()</code>
0106: */
0107: public static final int COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = 2;
0108:
0109: /**
0110: * Row caption mode: The row headers are hidden. <b>This is the default
0111: * mode. </b>
0112: */
0113: public static final int ROW_HEADER_MODE_HIDDEN = -1;
0114:
0115: /**
0116: * Row caption mode: Items Id-objects toString() is used as row caption.
0117: */
0118: public static final int ROW_HEADER_MODE_ID = Select.ITEM_CAPTION_MODE_ID;
0119:
0120: /**
0121: * Row caption mode: Item-objects toString() is used as row caption.
0122: */
0123: public static final int ROW_HEADER_MODE_ITEM = Select.ITEM_CAPTION_MODE_ITEM;
0124:
0125: /**
0126: * Row caption mode: Index of the item is used as item caption. * The index
0127: * mode can only be used with the containers implementing Container.Indexed
0128: * interface.
0129: */
0130: public static final int ROW_HEADER_MODE_INDEX = Select.ITEM_CAPTION_MODE_INDEX;
0131:
0132: /**
0133: * Row caption mode: Item captions are explicitly specified.
0134: */
0135: public static final int ROW_HEADER_MODE_EXPLICIT = Select.ITEM_CAPTION_MODE_EXPLICIT;
0136:
0137: /**
0138: * Row caption mode: Item captions are read from property specified with
0139: * <code>setItemCaptionPropertyId</code>.
0140: */
0141: public static final int ROW_HEADER_MODE_PROPERTY = Select.ITEM_CAPTION_MODE_PROPERTY;
0142:
0143: /**
0144: * Row caption mode: Only icons are shown, the captions are hidden.
0145: */
0146: public static final int ROW_HEADER_MODE_ICON_ONLY = Select.ITEM_CAPTION_MODE_ICON_ONLY;
0147:
0148: /**
0149: * Row caption mode: Item captions are explicitly specified, but if the
0150: * caption is missing, the item id objects <code>toString()</code> is used
0151: * instead.
0152: */
0153: public static final int ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = Select.ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID;
0154:
0155: /* Private table extensions to Select *********************************** */
0156:
0157: /** True if column collapsing is allowed */
0158: private boolean columnCollapsingAllowed = false;
0159:
0160: /** True if reordering of columns is allowed on the client side */
0161: private boolean columnReorderingAllowed = false;
0162:
0163: /** Keymapper for column ids */
0164: private KeyMapper columnIdMap = new KeyMapper();
0165:
0166: /** Holds visible column propertyIds - in order */
0167: private LinkedList visibleColumns = new LinkedList();
0168:
0169: /** Holds propertyIds of currently collapsed columns */
0170: private HashSet collapsedColumns = new HashSet();
0171:
0172: /** Holds headers for visible columns (by propertyId) */
0173: private HashMap columnHeaders = new HashMap();
0174:
0175: /** Holds icons for visible columns (by propertyId) */
0176: private HashMap columnIcons = new HashMap();
0177:
0178: /** Holds alignments for visible columns (by propertyId) */
0179: private HashMap columnAlignments = new HashMap();
0180:
0181: /** Holds value of property pageLength. 0 disables paging. */
0182: private int pageLength = 0;
0183:
0184: /** Id the first item on the current page. */
0185: private Object currentPageFirstItemId = null;
0186:
0187: /** Index of the first item on the current page. */
0188: private int currentPageFirstItemIndex = 0;
0189:
0190: /** Holds value of property pageBuffering. */
0191: private boolean pageBuffering = false;
0192:
0193: /** Holds value of property selectable. */
0194: private boolean selectable = false;
0195:
0196: /** Holds value of property columnHeaderMode. */
0197: private int columnHeaderMode = COLUMN_HEADER_MODE_HIDDEN;
0198:
0199: /** True iff the row captions are hidden. */
0200: private boolean rowCaptionsAreHidden = true;
0201:
0202: /** Page contents buffer used in buffered mode */
0203: private Object[][] pageBuffer = null;
0204:
0205: /**
0206: * List of properties listened - the list is kept to release the listeners
0207: * later.
0208: */
0209: private LinkedList listenedProperties = null;
0210:
0211: /** List of visible components - the is used for needsRepaint calculation. */
0212: private LinkedList visibleComponents = null;
0213:
0214: /** List of action handlers */
0215: private LinkedList actionHandlers = null;
0216:
0217: /** Action mapper */
0218: private KeyMapper actionMapper = null;
0219:
0220: /** Table cell editor factory */
0221: private FieldFactory fieldFactory = new BaseFieldFactory();
0222:
0223: /** Is table editable */
0224: private boolean editable = false;
0225:
0226: /** Current sorting direction */
0227: private boolean sortAscending = true;
0228:
0229: /** Currently table is sorted on this propertyId */
0230: private Object sortContainerPropertyId = null;
0231:
0232: /** Is table sorting disabled alltogether; even if some of the properties would be
0233: * sortable. */
0234: private boolean sortDisabled = false;
0235:
0236: /* Table constructors *************************************************** */
0237:
0238: /** Create new empty table */
0239: public Table() {
0240: setRowHeaderMode(ROW_HEADER_MODE_HIDDEN);
0241: }
0242:
0243: /** Create new empty table with caption. */
0244: public Table(String caption) {
0245: this ();
0246: setCaption(caption);
0247: }
0248:
0249: /** Create new table with caption and connect it to a Container. */
0250: public Table(String caption, Container dataSource) {
0251: this ();
0252: setCaption(caption);
0253: setContainerDataSource(dataSource);
0254: }
0255:
0256: /* Table functionality ************************************************** */
0257:
0258: /**
0259: * Get the array of visible column property id:s.
0260: *
0261: * <p>
0262: * The columns are show in the order of their appearance in this array
0263: * </p>
0264: *
0265: * @return Value of property availableColumns.
0266: */
0267: public Object[] getVisibleColumns() {
0268: if (this .visibleColumns == null) {
0269: return null;
0270: }
0271: return this .visibleColumns.toArray();
0272: }
0273:
0274: /**
0275: * Set the array of visible column property id:s.
0276: *
0277: * <p>
0278: * The columns are show in the order of their appearance in this array
0279: * </p>
0280: *
0281: * @param availableColumns
0282: * Array of shown property id:s.
0283: */
0284: public void setVisibleColumns(Object[] visibleColumns) {
0285:
0286: // Visible columns must exist
0287: if (visibleColumns == null)
0288: throw new NullPointerException(
0289: "Can not set visible columns to null value");
0290:
0291: // Check that the new visible columns contains no nulls and properties
0292: // exist
0293: Collection properties = getContainerPropertyIds();
0294: for (int i = 0; i < visibleColumns.length; i++) {
0295: if (visibleColumns[i] == null)
0296: throw new NullPointerException(
0297: "Properties must be non-nulls");
0298: else if (!properties.contains(visibleColumns[i]))
0299: throw new IllegalArgumentException(
0300: "Properties must exist in the Container, missing property: "
0301: + visibleColumns[i]);
0302: }
0303:
0304: // If this is called befor the constructor is finished, it might be
0305: // uninitialized
0306: LinkedList newVC = new LinkedList();
0307: for (int i = 0; i < visibleColumns.length; i++) {
0308: newVC.add(visibleColumns[i]);
0309: }
0310:
0311: // Remove alignments, icons and headers from hidden columns
0312: if (this .visibleColumns != null)
0313: for (Iterator i = this .visibleColumns.iterator(); i
0314: .hasNext();) {
0315: Object col = i.next();
0316: if (!newVC.contains(col)) {
0317: setColumnHeader(col, null);
0318: setColumnAlignment(col, null);
0319: setColumnIcon(col, null);
0320: }
0321: }
0322:
0323: this .visibleColumns = newVC;
0324:
0325: // Assure visual refresh
0326: refreshCurrentPage();
0327: }
0328:
0329: /**
0330: * Get the headers of the columns.
0331: *
0332: * <p>
0333: * The headers match the property id:s given my the set visible column
0334: * headers. The table must be set in either
0335: * <code>ROW_HEADER_MODE_EXPLICIT</code> or
0336: * <code>ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code> mode to show the
0337: * headers. In the defaults mode any nulls in the headers array are replaced
0338: * with id.toString() outputs when rendering.
0339: * </p>
0340: *
0341: * @return Array of column headers.
0342: */
0343: public String[] getColumnHeaders() {
0344: if (this .columnHeaders == null) {
0345: return null;
0346: }
0347: String[] headers = new String[this .visibleColumns.size()];
0348: int i = 0;
0349: for (Iterator it = this .visibleColumns.iterator(); it.hasNext(); i++) {
0350: headers[i] = (String) this .columnHeaders.get(it.next());
0351: }
0352: return headers;
0353: }
0354:
0355: /**
0356: * Set the headers of the columns.
0357: *
0358: * <p>
0359: * The headers match the property id:s given my the set visible column
0360: * headers. The table must be set in either
0361: * <code>ROW_HEADER_MODE_EXPLICIT</code> or
0362: * <code>ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code> mode to show the
0363: * headers. In the defaults mode any nulls in the headers array are replaced
0364: * with id.toString() outputs when rendering.
0365: * </p>
0366: *
0367: * @param columnHeaders
0368: * Array of column headers that match the
0369: * <code>getVisibleColumns()</code>.
0370: */
0371: public void setColumnHeaders(String[] columnHeaders) {
0372:
0373: if (columnHeaders.length != this .visibleColumns.size())
0374: throw new IllegalArgumentException(
0375: "The length of the headers array must match the number of visible columns");
0376:
0377: this .columnHeaders.clear();
0378: int i = 0;
0379: for (Iterator it = this .visibleColumns.iterator(); it.hasNext()
0380: && i < columnHeaders.length; i++) {
0381: this .columnHeaders.put(it.next(), columnHeaders[i]);
0382: }
0383:
0384: // Assure visual refresh
0385: refreshCurrentPage();
0386: }
0387:
0388: /**
0389: * Get the icons of the columns.
0390: *
0391: * <p>
0392: * The icons in headers match the property id:s given my the set visible
0393: * column headers. The table must be set in either
0394: * <code>ROW_HEADER_MODE_EXPLICIT</code> or
0395: * <code>ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code> mode to show the
0396: * headers with icons.
0397: * </p>
0398: *
0399: * @return Array of icons that match the <code>getVisibleColumns()</code>.
0400: */
0401: public Resource[] getColumnIcons() {
0402: if (this .columnIcons == null) {
0403: return null;
0404: }
0405: Resource[] icons = new Resource[this .visibleColumns.size()];
0406: int i = 0;
0407: for (Iterator it = this .visibleColumns.iterator(); it.hasNext(); i++) {
0408: icons[i] = (Resource) this .columnIcons.get(it.next());
0409: }
0410:
0411: return icons;
0412: }
0413:
0414: /**
0415: * Set the icons of the columns.
0416: *
0417: * <p>
0418: * The icons in headers match the property id:s given my the set visible
0419: * column headers. The table must be set in either
0420: * <code>ROW_HEADER_MODE_EXPLICIT</code> or
0421: * <code>ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code> mode to show the
0422: * headers with icons.
0423: * </p>
0424: *
0425: * @param columnIcons
0426: * Array of icons that match the <code>getVisibleColumns()</code>.
0427: */
0428: public void setColumnIcons(Resource[] columnIcons) {
0429:
0430: if (columnIcons.length != this .visibleColumns.size())
0431: throw new IllegalArgumentException(
0432: "The length of the icons array must match the number of visible columns");
0433:
0434: this .columnIcons.clear();
0435: int i = 0;
0436: for (Iterator it = this .visibleColumns.iterator(); it.hasNext()
0437: && i < columnIcons.length; i++) {
0438: this .columnIcons.put(it.next(), columnIcons[i]);
0439: }
0440:
0441: // Assure visual refresh
0442: refreshCurrentPage();
0443: }
0444:
0445: /**
0446: * Get array of column alignments.
0447: *
0448: * <p>
0449: * The items in the array must match the properties identified by
0450: * <code>getVisibleColumns()</code>. The possible values for the
0451: * alignments include:
0452: * <ul>
0453: * <li><code>ALIGN_LEFT</code>: Left alignment</li>
0454: * <li><code>ALIGN_CENTER</code>: Centered</li>
0455: * <li><code>ALIGN_RIGHT</code>: Right alignment</li>
0456: * </ul>
0457: * The alignments default to <code>ALIGN_LEFT</code>: any null values are
0458: * rendered as align lefts.
0459: * </p>
0460: *
0461: * @return Column alignments array.
0462: */
0463: public String[] getColumnAlignments() {
0464: if (this .columnAlignments == null) {
0465: return null;
0466: }
0467: String[] alignments = new String[this .visibleColumns.size()];
0468: int i = 0;
0469: for (Iterator it = this .visibleColumns.iterator(); it.hasNext(); i++) {
0470: alignments[i++] = getColumnAlignment(it.next());
0471: }
0472:
0473: return alignments;
0474: }
0475:
0476: /**
0477: * Set the column alignments.
0478: *
0479: * <p>
0480: * The items in the array must match the properties identified by
0481: * <code>getVisibleColumns()</code>. The possible values for the
0482: * alignments include:
0483: * <ul>
0484: * <li><code>ALIGN_LEFT</code>: Left alignment</li>
0485: * <li><code>ALIGN_CENTER</code>: Centered</li>
0486: * <li><code>ALIGN_RIGHT</code>: Right alignment</li>
0487: * </ul>
0488: * The alignments default to <code>ALIGN_LEFT</code>
0489: * </p>
0490: *
0491: * @param columnAlignments
0492: * Column alignments array.
0493: */
0494: public void setColumnAlignments(String[] columnAlignments) {
0495:
0496: if (columnAlignments.length != this .visibleColumns.size())
0497: throw new IllegalArgumentException(
0498: "The length of the alignments array must match the number of visible columns");
0499:
0500: // Check all alignments
0501: for (int i = 0; i < columnAlignments.length; i++) {
0502: String a = columnAlignments[i];
0503: if (a != null && !a.equals(ALIGN_LEFT)
0504: && !a.equals(ALIGN_CENTER)
0505: && !a.equals(ALIGN_RIGHT))
0506: throw new IllegalArgumentException("Column " + i
0507: + " aligment '" + a + "' is invalid");
0508: }
0509:
0510: // Reset alignments
0511: HashMap newCA = new HashMap();
0512: int i = 0;
0513: for (Iterator it = this .visibleColumns.iterator(); it.hasNext()
0514: && i < columnAlignments.length; i++) {
0515: newCA.put(it.next(), columnAlignments[i]);
0516: }
0517: this .columnAlignments = newCA;
0518:
0519: // Assure visual refresh
0520: refreshCurrentPage();
0521: }
0522:
0523: /**
0524: * Get the page length.
0525: *
0526: * <p>
0527: * Setting page length 0 disables paging.
0528: * </p>
0529: *
0530: * @return Lenght of one page.
0531: */
0532: public int getPageLength() {
0533: return this .pageLength;
0534: }
0535:
0536: /**
0537: * Set the page length.
0538: *
0539: * <p>
0540: * Setting page length 0 disables paging. The page length defaults to 0 (no
0541: * paging).
0542: * </p>
0543: *
0544: * @param Lenght
0545: * of one page.
0546: */
0547: public void setPageLength(int pageLength) {
0548: if (pageLength >= 0 && this .pageLength != pageLength) {
0549: this .pageLength = pageLength;
0550:
0551: // Assure visual refresh
0552: refreshCurrentPage();
0553: }
0554: }
0555:
0556: /**
0557: * Getter for property currentPageFirstItem.
0558: *
0559: * @return Value of property currentPageFirstItem.
0560: */
0561: public Object getCurrentPageFirstItemId() {
0562:
0563: // Priorise index over id if indexes are supported
0564: if (items instanceof Container.Indexed) {
0565: int index = getCurrentPageFirstItemIndex();
0566: Object id = null;
0567: if (index >= 0 && index < size())
0568: id = ((Container.Indexed) items).getIdByIndex(index);
0569: if (id != null && !id.equals(currentPageFirstItemId))
0570: currentPageFirstItemId = id;
0571: }
0572:
0573: // If there is no item id at all, use the first one
0574: if (currentPageFirstItemId == null)
0575: currentPageFirstItemId = ((Container.Ordered) items)
0576: .firstItemId();
0577:
0578: return currentPageFirstItemId;
0579: }
0580:
0581: /**
0582: * Setter for property currentPageFirstItem.
0583: *
0584: * @param currentPageFirstItem
0585: * New value of property currentPageFirstItem.
0586: */
0587: public void setCurrentPageFirstItemId(Object currentPageFirstItemId) {
0588:
0589: // Get the corresponding index
0590: int index = -1;
0591: try {
0592: index = ((Container.Indexed) items)
0593: .indexOfId(currentPageFirstItemId);
0594: } catch (ClassCastException e) {
0595:
0596: // If the table item container does not have index, we have to
0597: // calculate the index by hand
0598: Object id = ((Container.Ordered) items).firstItemId();
0599: while (id != null && !id.equals(currentPageFirstItemId)) {
0600: index++;
0601: id = ((Container.Ordered) items).nextItemId(id);
0602: }
0603: if (id == null)
0604: index = -1;
0605: }
0606:
0607: // If the search for item index was successfull
0608: if (index >= 0) {
0609: this .currentPageFirstItemId = currentPageFirstItemId;
0610: this .currentPageFirstItemIndex = index;
0611: }
0612:
0613: // Assure visual refresh
0614: refreshCurrentPage();
0615:
0616: }
0617:
0618: /**
0619: * Gets the icon Resource for the specified column.
0620: *
0621: * @param propertyId
0622: * the propertyId indentifying the column.
0623: * @return the icon for the specified column; null if the column has no icon
0624: * set, or if the column is not visible.
0625: */
0626: public Resource getColumnIcon(Object propertyId) {
0627: return (Resource) this .columnIcons.get(propertyId);
0628: }
0629:
0630: /**
0631: * Sets the icon Resource for the specified column.
0632: * <p>
0633: * Throws IllegalArgumentException if the specified column is not visible.
0634: * </p>
0635: *
0636: * @param propertyId
0637: * the propertyId identifying the column.
0638: * @param icon
0639: * the icon Resource to set.
0640: */
0641: public void setColumnIcon(Object propertyId, Resource icon) {
0642:
0643: if (icon == null)
0644: this .columnIcons.remove(propertyId);
0645: else
0646: this .columnIcons.put(propertyId, icon);
0647:
0648: // Assure visual refresh
0649: refreshCurrentPage();
0650: }
0651:
0652: /**
0653: * Gets the header for the specified column.
0654: *
0655: * @param propertyId
0656: * the propertyId indentifying the column.
0657: * @return the header for the specifed column if it has one.
0658: */
0659: public String getColumnHeader(Object propertyId) {
0660: if (getColumnHeaderMode() == COLUMN_HEADER_MODE_HIDDEN)
0661: return null;
0662:
0663: String header = (String) this .columnHeaders.get(propertyId);
0664: if ((header == null && this .getColumnHeaderMode() == COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID)
0665: || this .getColumnHeaderMode() == COLUMN_HEADER_MODE_ID) {
0666: header = propertyId.toString();
0667: }
0668:
0669: return header;
0670: }
0671:
0672: /**
0673: * Sets column header for the specified column;
0674: *
0675: * @param propertyId
0676: * the propertyId indentifying the column.
0677: * @param header
0678: * the header to set.
0679: */
0680: public void setColumnHeader(Object propertyId, String header) {
0681:
0682: if (header == null) {
0683: this .columnHeaders.remove(propertyId);
0684: return;
0685: }
0686: this .columnHeaders.put(propertyId, header);
0687:
0688: // Assure visual refresh
0689: refreshCurrentPage();
0690: }
0691:
0692: /**
0693: * Gets the specified column's alignment.
0694: *
0695: * @param propertyId
0696: * the propertyID identifying the column.
0697: * @return the specified column's alignment if it as one; null otherwise.
0698: */
0699: public String getColumnAlignment(Object propertyId) {
0700: String a = (String) this .columnAlignments.get(propertyId);
0701: return a == null ? ALIGN_LEFT : a;
0702: }
0703:
0704: /**
0705: * Sets the specified column's alignment.
0706: *
0707: * <p>
0708: * Throws IllegalArgumentException if the alignment is not one of the
0709: * following: ALIGN_LEFT, ALIGN_CENTER or ALIGN_RIGHT
0710: * </p>
0711: *
0712: * @param propertyId
0713: * the propertyID identifying the column.
0714: * @param alignment
0715: * the desired alignment.
0716: */
0717: public void setColumnAlignment(Object propertyId, String alignment) {
0718:
0719: // Check for valid alignments
0720: if (alignment != null && !alignment.equals(ALIGN_LEFT)
0721: && !alignment.equals(ALIGN_CENTER)
0722: && !alignment.equals(ALIGN_RIGHT))
0723: throw new IllegalArgumentException("Column alignment '"
0724: + alignment + "' is not supported.");
0725:
0726: if (alignment == null || alignment.equals(ALIGN_LEFT)) {
0727: this .columnAlignments.remove(propertyId);
0728: return;
0729: }
0730:
0731: this .columnAlignments.put(propertyId, alignment);
0732:
0733: // Assure visual refresh
0734: refreshCurrentPage();
0735: }
0736:
0737: /**
0738: * Checks if the specified column is collapsed.
0739: *
0740: * @param propertyId
0741: * the propertyID identifying the column.
0742: * @return true if the column is collapsed; false otherwise;
0743: */
0744: public boolean isColumnCollapsed(Object propertyId) {
0745: return collapsedColumns != null
0746: && collapsedColumns.contains(propertyId);
0747: }
0748:
0749: /**
0750: * Sets whether the specified column is collapsed or not.
0751: *
0752: *
0753: * @param propertyId
0754: * the propertyID identifying the column.
0755: * @param collapsed
0756: * the desired collapsedness.
0757: */
0758: public void setColumnCollapsed(Object propertyId, boolean collapsed)
0759: throws IllegalAccessException {
0760: if (!this .isColumnCollapsingAllowed()) {
0761: throw new IllegalAccessException(
0762: "Column collapsing not allowed!");
0763: }
0764:
0765: if (collapsed)
0766: this .collapsedColumns.add(propertyId);
0767: else
0768: this .collapsedColumns.remove(propertyId);
0769:
0770: // Assure visual refresh
0771: refreshCurrentPage();
0772: }
0773:
0774: /**
0775: * Check if column collapsing is allowed.
0776: *
0777: * @return true if columns can be collapsed; false otherwise.
0778: */
0779: public boolean isColumnCollapsingAllowed() {
0780: return this .columnCollapsingAllowed;
0781: }
0782:
0783: /**
0784: * Sets whether column collapsing is allowed or not.
0785: *
0786: * @param collapsingAllowed
0787: * specifies whether column collapsing is allowed.
0788: */
0789: public void setColumnCollapsingAllowed(boolean collapsingAllowed) {
0790: this .columnCollapsingAllowed = collapsingAllowed;
0791:
0792: if (!collapsingAllowed)
0793: collapsedColumns.clear();
0794:
0795: // Assure visual refresh
0796: refreshCurrentPage();
0797: }
0798:
0799: /**
0800: * Check if column reordering is allowed.
0801: *
0802: * @return true if columns can be reordered; false otherwise.
0803: */
0804: public boolean isColumnReorderingAllowed() {
0805: return this .columnReorderingAllowed;
0806: }
0807:
0808: /**
0809: * Sets whether column reordering is allowed or not.
0810: *
0811: * @param reorderingAllowed
0812: * specifies whether column reordering is allowed.
0813: */
0814: public void setColumnReorderingAllowed(boolean reorderingAllowed) {
0815: this .columnReorderingAllowed = reorderingAllowed;
0816:
0817: // Assure visual refresh
0818: refreshCurrentPage();
0819: }
0820:
0821: /*
0822: * Arranges visible columns according to given columnOrder. Silently ignores
0823: * colimnId:s that are not visible columns, and keeps the internal order of
0824: * visible columns left out of the ordering (trailing). Silently does
0825: * nothing if columnReordering is not allowed.
0826: */
0827: private void setColumnOrder(Object[] columnOrder) {
0828: if (columnOrder == null || !this .isColumnReorderingAllowed()) {
0829: return;
0830: }
0831: LinkedList newOrder = new LinkedList();
0832: for (int i = 0; i < columnOrder.length; i++) {
0833: if (columnOrder[i] != null
0834: && this .visibleColumns.contains(columnOrder[i])) {
0835: this .visibleColumns.remove(columnOrder[i]);
0836: newOrder.add(columnOrder[i]);
0837: }
0838: }
0839: for (Iterator it = this .visibleColumns.iterator(); it.hasNext();) {
0840: Object columnId = it.next();
0841: if (!newOrder.contains(columnId))
0842: newOrder.add(columnId);
0843: }
0844: this .visibleColumns = newOrder;
0845:
0846: // Assure visual refresh
0847: refreshCurrentPage();
0848: }
0849:
0850: /**
0851: * Getter for property currentPageFirstItem.
0852: *
0853: * @return Value of property currentPageFirstItem.
0854: */
0855: public int getCurrentPageFirstItemIndex() {
0856: return this .currentPageFirstItemIndex;
0857: }
0858:
0859: /**
0860: * Setter for property currentPageFirstItem.
0861: *
0862: * @param newIndex
0863: * New value of property currentPageFirstItem.
0864: */
0865: public void setCurrentPageFirstItemIndex(int newIndex) {
0866:
0867: // Ensure that the new value is valid
0868: if (newIndex < 0)
0869: newIndex = 0;
0870: if (newIndex >= size())
0871: newIndex = size() - 1;
0872:
0873: // Refresh first item id
0874: if (items instanceof Container.Indexed) {
0875: try {
0876: currentPageFirstItemId = ((Container.Indexed) items)
0877: .getIdByIndex(newIndex);
0878: } catch (IndexOutOfBoundsException e) {
0879: currentPageFirstItemId = null;
0880: }
0881: this .currentPageFirstItemIndex = newIndex;
0882: } else {
0883:
0884: // For containers not supporting indexes, we must iterate the
0885: // container forwards / backwards
0886: // next available item forward or backward
0887:
0888: this .currentPageFirstItemId = ((Container.Ordered) items)
0889: .firstItemId();
0890:
0891: // Go forwards in the middle of the list (respect borders)
0892: while (this .currentPageFirstItemIndex < newIndex
0893: && !((Container.Ordered) items)
0894: .isLastId(currentPageFirstItemId)) {
0895: this .currentPageFirstItemIndex++;
0896: currentPageFirstItemId = ((Container.Ordered) items)
0897: .nextItemId(currentPageFirstItemId);
0898: }
0899:
0900: // If we did hit the border
0901: if (((Container.Ordered) items)
0902: .isLastId(currentPageFirstItemId)) {
0903: this .currentPageFirstItemIndex = size() - 1;
0904: }
0905:
0906: // Go backwards in the middle of the list (respect borders)
0907: while (this .currentPageFirstItemIndex > newIndex
0908: && !((Container.Ordered) items)
0909: .isFirstId(currentPageFirstItemId)) {
0910: this .currentPageFirstItemIndex--;
0911: currentPageFirstItemId = ((Container.Ordered) items)
0912: .prevItemId(currentPageFirstItemId);
0913: }
0914:
0915: // If we did hit the border
0916: if (((Container.Ordered) items)
0917: .isFirstId(currentPageFirstItemId)) {
0918: this .currentPageFirstItemIndex = 0;
0919: }
0920:
0921: // Go forwards once more
0922: while (this .currentPageFirstItemIndex < newIndex
0923: && !((Container.Ordered) items)
0924: .isLastId(currentPageFirstItemId)) {
0925: this .currentPageFirstItemIndex++;
0926: currentPageFirstItemId = ((Container.Ordered) items)
0927: .nextItemId(currentPageFirstItemId);
0928: }
0929:
0930: // If for some reason we do hit border again, override
0931: // the user index request
0932: if (((Container.Ordered) items)
0933: .isLastId(currentPageFirstItemId)) {
0934: newIndex = this .currentPageFirstItemIndex = size() - 1;
0935: }
0936: }
0937:
0938: // Assure visual refresh
0939: refreshCurrentPage();
0940: }
0941:
0942: /**
0943: * Getter for property pageBuffering.
0944: *
0945: * @return Value of property pageBuffering.
0946: */
0947: public boolean isPageBufferingEnabled() {
0948: return this .pageBuffering;
0949: }
0950:
0951: /**
0952: * Setter for property pageBuffering.
0953: *
0954: * @param pageBuffering
0955: * New value of property pageBuffering.
0956: */
0957: public void setPageBufferingEnabled(boolean pageBuffering) {
0958:
0959: this .pageBuffering = pageBuffering;
0960:
0961: // If page buffering is disabled, clear the buffer
0962: if (!pageBuffering)
0963: pageBuffer = null;
0964: }
0965:
0966: /**
0967: * Getter for property selectable.
0968: *
0969: * <p>
0970: * The table is not selectable by default.
0971: * </p>
0972: *
0973: * @return Value of property selectable.
0974: */
0975: public boolean isSelectable() {
0976: return this .selectable;
0977: }
0978:
0979: /**
0980: * Setter for property selectable.
0981: *
0982: * <p>
0983: * The table is not selectable by default.
0984: * </p>
0985: *
0986: * @param selectable
0987: * New value of property selectable.
0988: */
0989: public void setSelectable(boolean selectable) {
0990: if (this .selectable != selectable) {
0991: this .selectable = selectable;
0992: requestRepaint();
0993: }
0994: }
0995:
0996: /**
0997: * Getter for property columnHeaderMode.
0998: *
0999: * @return Value of property columnHeaderMode.
1000: */
1001: public int getColumnHeaderMode() {
1002: return this .columnHeaderMode;
1003: }
1004:
1005: /**
1006: * Setter for property columnHeaderMode.
1007: *
1008: * @param columnHeaderMode
1009: * New value of property columnHeaderMode.
1010: */
1011: public void setColumnHeaderMode(int columnHeaderMode) {
1012: if (columnHeaderMode >= COLUMN_HEADER_MODE_HIDDEN
1013: && columnHeaderMode <= COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID)
1014: this .columnHeaderMode = columnHeaderMode;
1015:
1016: // Assure visual refresh
1017: refreshCurrentPage();
1018: }
1019:
1020: /**
1021: * Refresh the current page contents. If the page buffering is turned off,
1022: * it is not necessary to call this explicitely.
1023: */
1024: public void refreshCurrentPage() {
1025:
1026: // Clear page buffer and notify about the change
1027: pageBuffer = null;
1028: requestRepaint();
1029: }
1030:
1031: /**
1032: * Set the row header mode.
1033: * <p>
1034: * The mode can be one of the following ones:
1035: * <ul>
1036: * <li><code>ROW_HEADER_MODE_HIDDEN</code>: The row captions are hidden.
1037: * </li>
1038: * <li><code>ROW_HEADER_MODE_ID</code>: Items Id-objects
1039: * <code>toString()</code> is used as row caption.
1040: * <li><code>ROW_HEADER_MODE_ITEM</code>: Item-objects
1041: * <code>toString()</code> is used as row caption.
1042: * <li><code>ROW_HEADER_MODE_PROPERTY</code>: Property set with
1043: * <code>setItemCaptionPropertyId()</code> is used as row header.
1044: * <li><code>ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID</code>: Items
1045: * Id-objects <code>toString()</code> is used as row header. If caption is
1046: * explicitly specified, it overrides the id-caption.
1047: * <li><code>ROW_HEADER_MODE_EXPLICIT</code>: The row headers must be
1048: * explicitly specified.</li>
1049: * <li><code>ROW_HEADER_MODE_INDEX</code>: The index of the item is used
1050: * as row caption. The index mode can only be used with the containers
1051: * implementing <code>Container.Indexed</code> interface.</li>
1052: * </ul>
1053: * The default value is <code>ROW_HEADER_MODE_HIDDEN</code>
1054: * </p>
1055: *
1056: * @param mode
1057: * One of the modes listed above.
1058: */
1059: public void setRowHeaderMode(int mode) {
1060: if (ROW_HEADER_MODE_HIDDEN == mode)
1061: rowCaptionsAreHidden = true;
1062: else {
1063: rowCaptionsAreHidden = false;
1064: setItemCaptionMode(mode);
1065: }
1066:
1067: // Assure visual refresh
1068: refreshCurrentPage();
1069: }
1070:
1071: /**
1072: * Get the row header mode.
1073: *
1074: * @return Row header mode.
1075: * @see #setRowHeaderMode(int)
1076: */
1077: public int getRowHeaderMode() {
1078: return rowCaptionsAreHidden ? ROW_HEADER_MODE_HIDDEN
1079: : getItemCaptionMode();
1080: }
1081:
1082: /**
1083: * Add new row to table and fill the visible cells with given values.
1084: *
1085: * @param cells
1086: * Object array that is used for filling the visible cells new
1087: * row. The types must be settable to visible column property
1088: * types.
1089: * @param itemId
1090: * Id the new row. If null, a new id is automatically assigned.
1091: * If given, the table cant already have a item with given id.
1092: * @return Returns item id for the new row. Returns null if operation fails.
1093: */
1094: public Object addItem(Object[] cells, Object itemId)
1095: throws UnsupportedOperationException {
1096:
1097: Object[] cols = getVisibleColumns();
1098:
1099: // Check that a correct number of cells are given
1100: if (cells.length != cols.length)
1101: return null;
1102:
1103: // Create new item
1104: Item item;
1105: if (itemId == null) {
1106: itemId = items.addItem();
1107: if (itemId == null)
1108: return null;
1109: item = items.getItem(itemId);
1110: } else
1111: item = items.addItem(itemId);
1112: if (item == null)
1113: return null;
1114:
1115: // Fill the item properties
1116: for (int i = 0; i < cols.length; i++)
1117: item.getItemProperty(cols[i]).setValue(cells[i]);
1118:
1119: return itemId;
1120: }
1121:
1122: /* Overriding select behavior******************************************** */
1123:
1124: /**
1125: * @see org.millstone.base.data.Container.Viewer#setContainerDataSource(Container)
1126: */
1127: public void setContainerDataSource(Container newDataSource) {
1128:
1129: if (newDataSource == null)
1130: newDataSource = new IndexedContainer();
1131:
1132: // Assure that the data source is ordered by making unordered
1133: // containers ordered by wrapping them
1134: if (newDataSource instanceof Container.Ordered)
1135: super .setContainerDataSource(newDataSource);
1136: else
1137: super .setContainerDataSource(new ContainerOrderedWrapper(
1138: newDataSource));
1139:
1140: // Reset page position
1141: currentPageFirstItemId = null;
1142: currentPageFirstItemIndex = 0;
1143:
1144: // Reset column properties
1145: if (this .collapsedColumns != null)
1146: this .collapsedColumns.clear();
1147: setVisibleColumns(getContainerPropertyIds().toArray());
1148:
1149: // Assure visual refresh
1150: refreshCurrentPage();
1151: }
1152:
1153: /* Component basics ***************************************************** */
1154:
1155: /**
1156: * Invoked when the value of a variable has changed.
1157: *
1158: * @param event
1159: * Variable change event containing the information about the
1160: * changed variable.
1161: */
1162: public void changeVariables(Object source, Map variables) {
1163:
1164: super .changeVariables(source, variables);
1165:
1166: // Page start index
1167: if (variables.containsKey("firstvisible")) {
1168: Integer value = (Integer) variables.get("firstvisible");
1169: if (value != null)
1170: setCurrentPageFirstItemIndex(value.intValue() - 1);
1171: }
1172:
1173: // Actions
1174: if (variables.containsKey("action")) {
1175: StringTokenizer st = new StringTokenizer((String) variables
1176: .get("action"), ",");
1177: if (st.countTokens() == 2) {
1178: Object itemId = itemIdMapper.get(st.nextToken());
1179: Action action = (Action) actionMapper.get(st
1180: .nextToken());
1181: if (action != null && containsId(itemId)
1182: && actionHandlers != null)
1183: for (Iterator i = actionHandlers.iterator(); i
1184: .hasNext();)
1185: ((Action.Handler) i.next()).handleAction(
1186: action, this , itemId);
1187: }
1188: }
1189:
1190: // Sorting
1191: boolean doSort = false;
1192: if (variables.containsKey("sortcolumn")) {
1193: String colId = (String) variables.get("sortcolumn");
1194: if (colId != null && !"".equals(colId)
1195: && !"null".equals(colId)) {
1196: Object id = this .columnIdMap.get(colId);
1197: setSortContainerPropertyId(id);
1198: doSort = true;
1199: }
1200: }
1201: if (variables.containsKey("sortascending")) {
1202: boolean state = ((Boolean) variables.get("sortascending"))
1203: .booleanValue();
1204: if (state != this .sortAscending) {
1205: setSortAscending(state);
1206: doSort = true;
1207: }
1208: }
1209: if (doSort)
1210: this .sort();
1211:
1212: // Dynamic column hide/show and order
1213: // Update visible columns
1214: if (this .isColumnCollapsingAllowed()) {
1215: if (variables.containsKey("collapsedcolumns")) {
1216: try {
1217: Object[] ids = (Object[]) variables
1218: .get("collapsedcolumns");
1219: for (Iterator it = this .visibleColumns.iterator(); it
1220: .hasNext();) {
1221: this .setColumnCollapsed(it.next(), false);
1222: }
1223: for (int i = 0; i < ids.length; i++) {
1224: this .setColumnCollapsed(columnIdMap.get(ids[i]
1225: .toString()), true);
1226: }
1227: } catch (Exception ignored) {
1228: }
1229: }
1230: }
1231: if (this .isColumnReorderingAllowed()) {
1232: if (variables.containsKey("columnorder")) {
1233: try {
1234: Object[] ids = (Object[]) variables
1235: .get("columnorder");
1236: for (int i = 0; i < ids.length; i++) {
1237: ids[i] = columnIdMap.get(ids[i].toString());
1238: }
1239: this .setColumnOrder(ids);
1240: } catch (Exception ignored) {
1241: }
1242: }
1243: }
1244: }
1245:
1246: /**
1247: * Paint the content of this component.
1248: *
1249: * @param target
1250: * Paint target.
1251: * @throws PaintException
1252: * The paint operation failed.
1253: */
1254: public void paintContent(PaintTarget target) throws PaintException {
1255:
1256: // Focus control id
1257: if (this .getFocusableId() > 0) {
1258: target.addAttribute("focusid", this .getFocusableId());
1259: }
1260:
1261: // The tab ordering number
1262: if (this .getTabIndex() > 0)
1263: target.addAttribute("tabindex", this .getTabIndex());
1264:
1265: // Initialize temps
1266: Object[] colids = getVisibleColumns();
1267: int cols = colids.length;
1268: int first = getCurrentPageFirstItemIndex();
1269: int total = size();
1270: int pagelen = getPageLength();
1271: int colHeadMode = getColumnHeaderMode();
1272: boolean colheads = colHeadMode != COLUMN_HEADER_MODE_HIDDEN;
1273: boolean rowheads = getRowHeaderMode() != ROW_HEADER_MODE_HIDDEN;
1274: Object[][] cells = getVisibleCells();
1275: boolean iseditable = this .isEditable();
1276:
1277: // selection support
1278: String[] selectedKeys;
1279: if (isMultiSelect())
1280: selectedKeys = new String[((Set) getValue()).size()];
1281: else
1282: selectedKeys = new String[(getValue() == null
1283: && getNullSelectionItemId() == null ? 0 : 1)];
1284: int keyIndex = 0;
1285:
1286: // Table attributes
1287: if (isSelectable())
1288: target.addAttribute("selectmode",
1289: (isMultiSelect() ? "multi" : "single"));
1290: else
1291: target.addAttribute("selectmode", "none");
1292: target.addAttribute("cols", cols);
1293: target.addAttribute("rows", cells[0].length);
1294: target.addAttribute("totalrows", total);
1295: if (pagelen != 0)
1296: target.addAttribute("pagelength", pagelen);
1297: if (colheads)
1298: target.addAttribute("colheaders", true);
1299: if (rowheads)
1300: target.addAttribute("rowheaders", true);
1301:
1302: // Columns
1303: target.startTag("cols");
1304: Collection sortables = getSortableContainerPropertyIds();
1305: for (Iterator it = this .visibleColumns.iterator(); it.hasNext();) {
1306: Object columnId = it.next();
1307: if (!isColumnCollapsed(columnId)) {
1308: target.startTag("ch");
1309: if (colheads) {
1310: if (this .getColumnIcon(columnId) != null)
1311: target.addAttribute("icon", this
1312: .getColumnIcon(columnId));
1313: if (sortables.contains(columnId))
1314: target.addAttribute("sortable", true);
1315: String header = (String) this
1316: .getColumnHeader(columnId);
1317: target.addAttribute("caption",
1318: (header != null ? header : ""));
1319: }
1320: target.addAttribute("cid", this .columnIdMap
1321: .key(columnId));
1322: if (!ALIGN_LEFT.equals(this
1323: .getColumnAlignment(columnId)))
1324: target.addAttribute("align", this
1325: .getColumnAlignment(columnId));
1326: target.endTag("ch");
1327: }
1328: }
1329: target.endTag("cols");
1330:
1331: // Rows
1332: Set actionSet = new LinkedHashSet();
1333: boolean selectable = isSelectable();
1334: boolean[] iscomponent = new boolean[this .visibleColumns.size()];
1335: int iscomponentIndex = 0;
1336: for (Iterator it = this .visibleColumns.iterator(); it.hasNext()
1337: && iscomponentIndex < iscomponent.length;) {
1338: Object columnId = it.next();
1339: Class colType = getType(columnId);
1340: iscomponent[iscomponentIndex++] = colType != null
1341: && Component.class.isAssignableFrom(colType);
1342: }
1343: target.startTag("rows");
1344: for (int i = 0; i < cells[0].length; i++) {
1345: target.startTag("tr");
1346: Object itemId = cells[CELL_ITEMID][i];
1347:
1348: // tr attributes
1349: if (rowheads) {
1350: if (cells[CELL_ICON][i] != null)
1351: target.addAttribute("icon",
1352: (Resource) cells[CELL_ICON][i]);
1353: if (cells[CELL_HEADER][i] != null)
1354: target.addAttribute("caption",
1355: (String) cells[CELL_HEADER][i]);
1356: }
1357: if (actionHandlers != null || isSelectable()) {
1358: target.addAttribute("key", (String) cells[CELL_KEY][i]);
1359: if (isSelected(itemId)
1360: && keyIndex < selectedKeys.length) {
1361: target.addAttribute("selected", true);
1362: selectedKeys[keyIndex++] = (String) cells[CELL_KEY][i];
1363: }
1364: }
1365:
1366: // Actions
1367: if (actionHandlers != null) {
1368: target.startTag("al");
1369: for (Iterator ahi = actionHandlers.iterator(); ahi
1370: .hasNext();) {
1371: Action[] aa = ((Action.Handler) ahi.next())
1372: .getActions(itemId, this );
1373: if (aa != null)
1374: for (int ai = 0; ai < aa.length; ai++) {
1375: String key = actionMapper.key(aa[ai]);
1376: actionSet.add(aa[ai]);
1377: target.addSection("ak", key);
1378: }
1379: }
1380: target.endTag("al");
1381: }
1382:
1383: // cells
1384: int currentColumn = 0;
1385: for (Iterator it = this .visibleColumns.iterator(); it
1386: .hasNext(); currentColumn++) {
1387: Object columnId = it.next();
1388: if (columnId == null
1389: || this .isColumnCollapsed(columnId))
1390: continue;
1391: if ((iscomponent[currentColumn] || iseditable)
1392: && Component.class
1393: .isInstance(cells[CELL_FIRSTCOL
1394: + currentColumn][i])) {
1395: Component c = (Component) cells[CELL_FIRSTCOL
1396: + currentColumn][i];
1397: if (c == null)
1398: target.addSection("label", "");
1399: else
1400: c.paint(target);
1401: } else
1402: target.addSection("label",
1403: (String) cells[CELL_FIRSTCOL
1404: + currentColumn][i]);
1405: }
1406:
1407: target.endTag("tr");
1408: }
1409: target.endTag("rows");
1410:
1411: // The select variable is only enabled if selectable
1412: if (selectable)
1413: target.addVariable(this , "selected", selectedKeys);
1414:
1415: // The cursors are only shown on pageable table
1416: if (first != 0 || getPageLength() > 0)
1417: target.addVariable(this , "firstvisible", first + 1);
1418:
1419: // Sorting
1420: if (getContainerDataSource() instanceof Container.Sortable) {
1421: target.addVariable(this , "sortcolumn", this .columnIdMap
1422: .key(this .sortContainerPropertyId));
1423: target.addVariable(this , "sortascending",
1424: this .sortAscending);
1425: }
1426:
1427: // Actions
1428: if (!actionSet.isEmpty()) {
1429: target.startTag("actions");
1430: target.addVariable(this , "action", "");
1431: for (Iterator it = actionSet.iterator(); it.hasNext();) {
1432: Action a = (Action) it.next();
1433: target.startTag("action");
1434: if (a.getCaption() != null)
1435: target.addAttribute("caption", a.getCaption());
1436: if (a.getIcon() != null)
1437: target.addAttribute("icon", a.getIcon());
1438: target.addAttribute("key", actionMapper.key(a));
1439: target.endTag("action");
1440: }
1441: target.endTag("actions");
1442: }
1443: if (this .columnReorderingAllowed) {
1444: String[] colorder = new String[this .visibleColumns.size()];
1445: int i = 0;
1446: for (Iterator it = this .visibleColumns.iterator(); it
1447: .hasNext()
1448: && i < colorder.length;) {
1449: colorder[i++] = this .columnIdMap.key(it.next());
1450: }
1451: target.addVariable(this , "columnorder", colorder);
1452: }
1453: // Available columns
1454: if (this .columnCollapsingAllowed) {
1455: HashSet ccs = new HashSet();
1456: for (Iterator i = visibleColumns.iterator(); i.hasNext();) {
1457: Object o = i.next();
1458: if (isColumnCollapsed(o))
1459: ccs.add(o);
1460: }
1461: String[] collapsedkeys = new String[ccs.size()];
1462: int nextColumn = 0;
1463: for (Iterator it = this .visibleColumns.iterator(); it
1464: .hasNext()
1465: && nextColumn < collapsedkeys.length;) {
1466: Object columnId = it.next();
1467: if (this .isColumnCollapsed(columnId)) {
1468: collapsedkeys[nextColumn++] = this .columnIdMap
1469: .key(columnId);
1470: }
1471: }
1472: target.addVariable(this , "collapsedcolumns", collapsedkeys);
1473: target.startTag("visiblecolumns");
1474: int i = 0;
1475: for (Iterator it = this .visibleColumns.iterator(); it
1476: .hasNext(); i++) {
1477: Object columnId = it.next();
1478: if (columnId != null) {
1479: target.startTag("column");
1480: target.addAttribute("cid", this .columnIdMap
1481: .key(columnId));
1482: String head = getColumnHeader(columnId);
1483: target.addAttribute("caption", (head != null ? head
1484: : ""));
1485: if (this .isColumnCollapsed(columnId)) {
1486: target.addAttribute("collapsed", true);
1487: }
1488: target.endTag("column");
1489: }
1490: }
1491: target.endTag("visiblecolumns");
1492: }
1493: }
1494:
1495: /**
1496: * Get UIDL tag corresponding to component.
1497: *
1498: * @return UIDL tag as string.
1499: */
1500: public String getTag() {
1501: return "table";
1502: }
1503:
1504: /** Return cached visible table contents */
1505: private Object[][] getVisibleCells() {
1506:
1507: // Return a buffered value if possible
1508: if (pageBuffer != null && isPageBufferingEnabled())
1509: return pageBuffer;
1510:
1511: // Stop listening the old properties and initialise the list
1512: if (listenedProperties == null)
1513: listenedProperties = new LinkedList();
1514: else
1515: for (Iterator i = listenedProperties.iterator(); i
1516: .hasNext();) {
1517: ((Property.ValueChangeNotifier) i.next())
1518: .removeListener(this );
1519: }
1520:
1521: // Detach old visible component from the table
1522: if (visibleComponents == null)
1523: visibleComponents = new LinkedList();
1524: else
1525: for (Iterator i = visibleComponents.iterator(); i.hasNext();) {
1526: ((Component) i.next()).setParent(null);
1527: }
1528:
1529: // Collect basic facts about the table page
1530: Object[] colids = getVisibleColumns();
1531: int cols = colids.length;
1532: int pagelen = getPageLength();
1533: int firstIndex = getCurrentPageFirstItemIndex();
1534: int rows = size();
1535: if (rows > 0 && firstIndex >= 0)
1536: rows -= firstIndex;
1537:
1538: if (pagelen > 0 && pagelen < rows)
1539: rows = pagelen;
1540: Object[][] cells = new Object[cols + CELL_FIRSTCOL][rows];
1541: if (rows == 0)
1542: return cells;
1543: Object id = getCurrentPageFirstItemId();
1544: int headmode = getRowHeaderMode();
1545: boolean[] iscomponent = new boolean[cols];
1546: for (int i = 0; i < cols; i++)
1547: iscomponent[i] = Component.class
1548: .isAssignableFrom(getType(colids[i]));
1549:
1550: // Create page contents
1551: int filledRows = 0;
1552: for (int i = 0; i < rows && id != null; i++) {
1553: cells[CELL_ITEMID][i] = id;
1554: cells[CELL_KEY][i] = itemIdMapper.key(id);
1555: if (headmode != ROW_HEADER_MODE_HIDDEN) {
1556: switch (headmode) {
1557: case ROW_HEADER_MODE_INDEX:
1558: cells[CELL_HEADER][i] = String.valueOf(i
1559: + firstIndex + 1);
1560: break;
1561: default:
1562: cells[CELL_HEADER][i] = getItemCaption(id);
1563: }
1564: cells[CELL_ICON][i] = getItemIcon(id);
1565: }
1566: if (cols > 0) {
1567: for (int j = 0; j < cols; j++) {
1568: Object value = null;
1569: Property p = getContainerProperty(id, colids[j]);
1570: if (p != null) {
1571: if (p instanceof Property.ValueChangeNotifier) {
1572: ((Property.ValueChangeNotifier) p)
1573: .addListener(this );
1574: listenedProperties.add(p);
1575: }
1576: if (iscomponent[j]) {
1577: value = p.getValue();
1578: } else if (p != null) {
1579: value = getPropertyValue(id, colids[j], p);
1580: } else {
1581: value = getPropertyValue(id, colids[j],
1582: null);
1583: }
1584: } else {
1585: value = "";
1586: }
1587:
1588: if (value instanceof Component) {
1589: ((Component) value).setParent(this );
1590: visibleComponents.add((Component) value);
1591: }
1592: cells[CELL_FIRSTCOL + j][i] = value;
1593:
1594: }
1595: }
1596: id = ((Container.Ordered) items).nextItemId(id);
1597:
1598: filledRows++;
1599: }
1600:
1601: // Assure that all the rows of the cell-buffer are valid
1602: if (filledRows != cells[0].length) {
1603: Object[][] temp = new Object[cells.length][filledRows];
1604: for (int i = 0; i < cells.length; i++)
1605: for (int j = 0; j < filledRows; j++)
1606: temp[i][j] = cells[i][j];
1607: cells = temp;
1608: }
1609:
1610: // Save the results to internal buffer iff in buffering mode
1611: // to possible conserve memory from large non-buffered pages
1612: if (isPageBufferingEnabled())
1613: pageBuffer = cells;
1614:
1615: return cells;
1616: }
1617:
1618: /**
1619: * Get value of property.
1620: *
1621: * By default if the table is editable the fieldFactory is used to create
1622: * editors for table cells. Otherwise formatPropertyValue is used to format
1623: * the value representation.
1624: *
1625: * @see #setFieldFactory(FieldFactory)
1626: * @param rowId
1627: * Id of the row (same as item Id)
1628: * @param colId
1629: * Id of the column
1630: * @param property
1631: * Property to be presented
1632: * @return Object Either formatted value or Component for field.
1633: */
1634: protected Object getPropertyValue(Object rowId, Object colId,
1635: Property property) {
1636: if (this .isEditable() && this .fieldFactory != null) {
1637: Field f = this .fieldFactory.createField(
1638: getContainerDataSource(), rowId, colId, this );
1639: if (f != null) {
1640: f.setPropertyDataSource(property);
1641: return f;
1642: }
1643: }
1644:
1645: return formatPropertyValue(rowId, colId, property);
1646: }
1647:
1648: /**
1649: * Formats table cell property values. By default the property.toString()
1650: * and return a empty string for null properties.
1651: *
1652: * @param itemId
1653: * @param property
1654: * Property to be formatted
1655: * @return String representation of property and its value.
1656: * @since 3.1
1657: */
1658: protected String formatPropertyValue(Object rowId, Object colId,
1659: Property property) {
1660: if (property == null) {
1661: return "";
1662: }
1663: return property.toString();
1664: }
1665:
1666: /* Action container *************************************************** */
1667:
1668: /**
1669: * @see org.millstone.base.event.Action.Container#addActionHandler(Action.Handler)
1670: */
1671: public void addActionHandler(Action.Handler actionHandler) {
1672:
1673: if (actionHandler != null) {
1674:
1675: if (actionHandlers == null) {
1676: actionHandlers = new LinkedList();
1677: actionMapper = new KeyMapper();
1678: }
1679:
1680: if (!actionHandlers.contains(actionHandler)) {
1681: actionHandlers.add(actionHandler);
1682: requestRepaint();
1683: }
1684:
1685: }
1686: }
1687:
1688: /**
1689: * @see org.millstone.base.event.Action.Container#removeActionHandler(Action.Handler)
1690: */
1691: public void removeActionHandler(Action.Handler actionHandler) {
1692:
1693: if (actionHandlers != null
1694: && actionHandlers.contains(actionHandler)) {
1695:
1696: actionHandlers.remove(actionHandler);
1697:
1698: if (actionHandlers.isEmpty()) {
1699: actionHandlers = null;
1700: actionMapper = null;
1701: }
1702:
1703: requestRepaint();
1704: }
1705: }
1706:
1707: /* Property value change listening support **************************** */
1708:
1709: /**
1710: * @see org.millstone.base.data.Property.ValueChangeListener#valueChange(Property.ValueChangeEvent)
1711: */
1712: public void valueChange(Property.ValueChangeEvent event) {
1713: super .valueChange(event);
1714: requestRepaint();
1715: }
1716:
1717: /**
1718: * @see org.millstone.base.ui.Component#attach()
1719: */
1720: public void attach() {
1721: super .attach();
1722:
1723: if (visibleComponents != null)
1724: for (Iterator i = visibleComponents.iterator(); i.hasNext();)
1725: ((Component) i.next()).attach();
1726: }
1727:
1728: /**
1729: * @see org.millstone.base.ui.Component#attach()
1730: */
1731: public void detach() {
1732: super .detach();
1733:
1734: if (visibleComponents != null)
1735: for (Iterator i = visibleComponents.iterator(); i.hasNext();)
1736: ((Component) i.next()).detach();
1737: }
1738:
1739: /**
1740: * @see org.millstone.base.data.Container#removeAllItems()
1741: */
1742: public boolean removeAllItems() {
1743: this .currentPageFirstItemId = null;
1744: this .currentPageFirstItemIndex = 0;
1745: return super .removeAllItems();
1746: }
1747:
1748: /**
1749: * @see org.millstone.base.data.Container#removeItem(Object)
1750: */
1751: public boolean removeItem(Object itemId) {
1752: Object nextItemId = ((Container.Ordered) items)
1753: .nextItemId(itemId);
1754: boolean ret = super .removeItem(itemId);
1755: if (ret && (itemId != null)
1756: && (itemId.equals(this .currentPageFirstItemId))) {
1757: this .currentPageFirstItemId = nextItemId;
1758: }
1759: return ret;
1760: }
1761:
1762: /**
1763: * @see org.millstone.base.data.Container#removeContainerProperty(Object)
1764: */
1765: public boolean removeContainerProperty(Object propertyId)
1766: throws UnsupportedOperationException {
1767:
1768: // If a visible property is removed, remove the corresponding column
1769: this .visibleColumns.remove(propertyId);
1770: this .columnAlignments.remove(propertyId);
1771: this .columnIcons.remove(propertyId);
1772: this .columnHeaders.remove(propertyId);
1773:
1774: return super .removeContainerProperty(propertyId);
1775: }
1776:
1777: /**
1778: * Adds a new property to the table and show it as a visible column.
1779: *
1780: * @see org.millstone.base.data.Container#addContainerProperty(Object,
1781: * Class, Object)
1782: *
1783: * @param propertyId
1784: * Id of the proprty
1785: * @param type
1786: * The class of the property
1787: * @param defaultValue
1788: * The default value given for all existing items
1789: */
1790: public boolean addContainerProperty(Object propertyId, Class type,
1791: Object defaultValue) throws UnsupportedOperationException {
1792: if (!super .addContainerProperty(propertyId, type, defaultValue))
1793: return false;
1794: if (!this .visibleColumns.contains(propertyId))
1795: this .visibleColumns.add(propertyId);
1796: return true;
1797: }
1798:
1799: /**
1800: * Adds a new property to the table and show it as a visible column.
1801: *
1802: * @see org.millstone.base.data.Container#addContainerProperty(Object,
1803: * Class, Object)
1804: *
1805: * @param propertyId
1806: * Id of the proprty
1807: * @param type
1808: * The class of the property
1809: * @param defaultValue
1810: * The default value given for all existing items
1811: * @param columnHeader
1812: * Explicit header of the column. If explicit header is not
1813: * needed, this should be set null.
1814: * @param columnIcon
1815: * Icon of the column. If icon is not needed, this should be set
1816: * null.
1817: * @param columnAlignment
1818: * Alignment of the column. Null implies align left.
1819: */
1820: public boolean addContainerProperty(Object propertyId, Class type,
1821: Object defaultValue, String columnHeader,
1822: Resource columnIcon, String columnAlignment)
1823: throws UnsupportedOperationException {
1824: if (!this .addContainerProperty(propertyId, type, defaultValue))
1825: return false;
1826: this .setColumnAlignment(propertyId, columnAlignment);
1827: this .setColumnHeader(propertyId, columnHeader);
1828: this .setColumnIcon(propertyId, columnIcon);
1829: return true;
1830: }
1831:
1832: /**
1833: * Return list of items on the current page
1834: *
1835: * @see org.millstone.base.ui.Select#getVisibleItemIds()
1836: */
1837: public Collection getVisibleItemIds() {
1838:
1839: LinkedList visible = new LinkedList();
1840:
1841: Object[][] cells = getVisibleCells();
1842: for (int i = 0; i < cells[CELL_ITEMID].length; i++)
1843: visible.add(cells[CELL_ITEMID][i]);
1844:
1845: return visible;
1846: }
1847:
1848: /**
1849: * Container datasource item set change. Table must flush its buffers on
1850: * change.
1851: *
1852: * @see org.millstone.base.data.Container.ItemSetChangeListener#containerItemSetChange(org.millstone.base.data.Container.ItemSetChangeEvent)
1853: */
1854: public void containerItemSetChange(
1855: Container.ItemSetChangeEvent event) {
1856: pageBuffer = null;
1857: super .containerItemSetChange(event);
1858: setCurrentPageFirstItemIndex(this
1859: .getCurrentPageFirstItemIndex());
1860: }
1861:
1862: /**
1863: * Container datasource property set change. Table must flush its buffers on
1864: * change.
1865: *
1866: * @see org.millstone.base.data.Container.PropertySetChangeListener#containerPropertySetChange(org.millstone.base.data.Container.PropertySetChangeEvent)
1867: */
1868: public void containerPropertySetChange(
1869: Container.PropertySetChangeEvent event) {
1870: pageBuffer = null;
1871: super .containerPropertySetChange(event);
1872: }
1873:
1874: /**
1875: * Adding new items is not supported.
1876: *
1877: * @see org.millstone.base.ui.Select#setNewItemsAllowed(boolean)
1878: * @throws UnsupportedOperationException
1879: * if set to true.
1880: */
1881: public void setNewItemsAllowed(boolean allowNewOptions)
1882: throws UnsupportedOperationException {
1883: if (allowNewOptions)
1884: throw new UnsupportedOperationException();
1885: }
1886:
1887: /**
1888: * Focusing to this component is not supported.
1889: *
1890: * @see org.millstone.base.ui.AbstractField#focus()
1891: * @throws UnsupportedOperationException
1892: * if invoked.
1893: */
1894: public void focus() throws UnsupportedOperationException {
1895: throw new UnsupportedOperationException();
1896: }
1897:
1898: /**
1899: * @see org.millstone.base.data.Container.Ordered#nextItemId(java.lang.Object)
1900: */
1901: public Object nextItemId(Object itemId) {
1902: return ((Container.Ordered) items).nextItemId(itemId);
1903: }
1904:
1905: /**
1906: * @see org.millstone.base.data.Container.Ordered#prevItemId(java.lang.Object)
1907: */
1908: public Object prevItemId(Object itemId) {
1909: return ((Container.Ordered) items).prevItemId(itemId);
1910: }
1911:
1912: /**
1913: * @see org.millstone.base.data.Container.Ordered#firstItemId()
1914: */
1915: public Object firstItemId() {
1916: return ((Container.Ordered) items).firstItemId();
1917: }
1918:
1919: /**
1920: * @see org.millstone.base.data.Container.Ordered#lastItemId()
1921: */
1922: public Object lastItemId() {
1923: return ((Container.Ordered) items).lastItemId();
1924: }
1925:
1926: /**
1927: * @see org.millstone.base.data.Container.Ordered#isFirstId(java.lang.Object)
1928: */
1929: public boolean isFirstId(Object itemId) {
1930: return ((Container.Ordered) items).isFirstId(itemId);
1931: }
1932:
1933: /**
1934: * @see org.millstone.base.data.Container.Ordered#isLastId(java.lang.Object)
1935: */
1936: public boolean isLastId(Object itemId) {
1937: return ((Container.Ordered) items).isLastId(itemId);
1938: }
1939:
1940: /**
1941: * @see org.millstone.base.data.Container.Ordered#addItemAfter(java.lang.Object)
1942: */
1943: public Object addItemAfter(Object previousItemId)
1944: throws UnsupportedOperationException {
1945: return ((Container.Ordered) items).addItemAfter(previousItemId);
1946: }
1947:
1948: /**
1949: * @see org.millstone.base.data.Container.Ordered#addItemAfter(java.lang.Object,
1950: * java.lang.Object)
1951: */
1952: public Item addItemAfter(Object previousItemId, Object newItemId)
1953: throws UnsupportedOperationException {
1954: return ((Container.Ordered) items).addItemAfter(previousItemId,
1955: newItemId);
1956: }
1957:
1958: /**
1959: * Get the FieldFactory that is used to create editor for table cells.
1960: *
1961: * The FieldFactory is only used if the Table is editable.
1962: *
1963: * @see #isEditable
1964: * @return FieldFactory used to create the Field instances.
1965: */
1966: public FieldFactory getFieldFactory() {
1967: return fieldFactory;
1968: }
1969:
1970: /**
1971: * Set the FieldFactory that is used to create editor for table cells.
1972: *
1973: * The FieldFactory is only used if the Table is editable. By default the
1974: * BaseFieldFactory is used.
1975: *
1976: * @see #isEditable
1977: * @see BaseFieldFactory
1978: * @param fieldFactory
1979: * The field factory to set
1980: */
1981: public void setFieldFactory(FieldFactory fieldFactory) {
1982: this .fieldFactory = fieldFactory;
1983:
1984: // Assure visual refresh
1985: refreshCurrentPage();
1986: }
1987:
1988: /**
1989: * Is table editable.
1990: *
1991: * If table is editable a editor of type Field is created for each table
1992: * cell. The assigned FieldFactory is used to create the instances.
1993: *
1994: * To provide custom editors for table cells create a class implementins the
1995: * FieldFactory interface, and assign it to table, and set the editable
1996: * property to true.
1997: *
1998: * @see Field
1999: * @see FieldFactory
2000: * @return true if table is editable, false oterwise.
2001: */
2002: public boolean isEditable() {
2003: return editable;
2004: }
2005:
2006: /**
2007: * Set the editable property.
2008: *
2009: * If table is editable a editor of type Field is created for each table
2010: * cell. The assigned FieldFactory is used to create the instances.
2011: *
2012: * To provide custom editors for table cells create a class implementins the
2013: * FieldFactory interface, and assign it to table, and set the editable
2014: * property to true.
2015: *
2016: * @see Field
2017: * @see FieldFactory
2018: * @param editable
2019: * true if table should be editable by user.
2020: */
2021: public void setEditable(boolean editable) {
2022: this .editable = editable;
2023:
2024: // Assure visual refresh
2025: refreshCurrentPage();
2026: }
2027:
2028: /**
2029: * Sort table.
2030: *
2031: * @see org.millstone.base.data.Container.Sortable#sort(java.lang.Object[],
2032: * boolean[])
2033: *
2034: * @throws UnsupportedOperationException
2035: * if the container data source does not implement
2036: * Container.Sortable
2037: */
2038: public void sort(Object[] propertyId, boolean[] ascending)
2039: throws UnsupportedOperationException {
2040: Container c = getContainerDataSource();
2041: if (c instanceof Container.Sortable) {
2042: int pageIndex = this .getCurrentPageFirstItemIndex();
2043: ((Container.Sortable) c).sort(propertyId, ascending);
2044: setCurrentPageFirstItemIndex(pageIndex);
2045: } else if (c != null) {
2046: throw new UnsupportedOperationException(
2047: "Underlying Data does not allow sorting");
2048: }
2049: }
2050:
2051: /**
2052: * Sort table by currently selected sorting column.
2053: *
2054: * @throws UnsupportedOperationException
2055: * if the container data source does not implement
2056: * Container.Sortable
2057: */
2058: public void sort() {
2059: if (getSortContainerPropertyId() == null)
2060: return;
2061: sort(new Object[] { this .sortContainerPropertyId },
2062: new boolean[] { this .sortAscending });
2063: }
2064:
2065: /*
2066: * (non-Javadoc)
2067: *
2068: * @see org.millstone.base.data.Container.Sortable#getSortableContainerPropertyIds()
2069: */
2070: public Collection getSortableContainerPropertyIds() {
2071: Container c = getContainerDataSource();
2072: if (c instanceof Container.Sortable && !isSortDisabled()) {
2073: return ((Container.Sortable) c)
2074: .getSortableContainerPropertyIds();
2075: } else {
2076: return new LinkedList();
2077: }
2078: }
2079:
2080: /**
2081: * Get the currently sorted column property ID.
2082: *
2083: * @return Container property id of the currently sorted column.
2084: */
2085: public Object getSortContainerPropertyId() {
2086: return this .sortContainerPropertyId;
2087: }
2088:
2089: /**
2090: * Set the currently sorted column property id.
2091: *
2092: * @param propertyId
2093: * Container property id of the currently sorted column.
2094: */
2095: public void setSortContainerPropertyId(Object propertyId) {
2096: if ((this .sortContainerPropertyId != null && !this .sortContainerPropertyId
2097: .equals(propertyId))
2098: || (this .sortContainerPropertyId == null && propertyId != null)) {
2099: this .sortContainerPropertyId = propertyId;
2100: sort();
2101: }
2102:
2103: // Assure visual refresh
2104: refreshCurrentPage();
2105: }
2106:
2107: /**
2108: * Is the table currently sorted in ascending order.
2109: *
2110: * @return <code>true</code> if ascending, <code>false</code> if
2111: * descending
2112: */
2113: public boolean isSortAscending() {
2114: return this .sortAscending;
2115: }
2116:
2117: /**
2118: * Set the table in ascending order.
2119: *
2120: * @param ascending
2121: * <code>true</code> if ascending, <code>false</code> if
2122: * descending
2123: */
2124: public void setSortAscending(boolean ascending) {
2125: if (this .sortAscending != ascending) {
2126: this .sortAscending = ascending;
2127: sort();
2128: }
2129:
2130: // Assure visual refresh
2131: refreshCurrentPage();
2132: }
2133:
2134: /** Is sorting disabled alltogether.
2135: *
2136: * True iff no sortable columns are given even in the case where datasource would support this.
2137: *
2138: * @return True iff sorting is disabled.
2139: */
2140: public boolean isSortDisabled() {
2141: return sortDisabled;
2142: }
2143:
2144: /** Disable sorting alltogether.
2145: *
2146: * To disable sorting alltogether, set to true. In this case no
2147: * sortable columns are given even in the case where datasource would support this.
2148: *
2149: * @param sortDisabled True iff sorting is disabled
2150: */
2151: public void setSortDisabled(boolean sortDisabled) {
2152: if (this.sortDisabled != sortDisabled) {
2153: this.sortDisabled = sortDisabled;
2154: refreshCurrentPage();
2155: }
2156: }
2157: }
|