/* * Copyright (c) 2003,2009 Declarative Engineering LLC. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Declarative Engineering LLC * verson 1 which accompanies this distribution, and is available at * http://declarativeengineering.com/legal/DE_Developer_License_v1.txt */ package com.foundation.tcv.swt.client; import com.common.comparison.Comparator; import com.common.util.IIterator; import com.common.util.IList; import com.common.util.LiteList; import com.common.util.optimized.IIntIterator; import com.common.util.optimized.IntObjectHashMap; import com.foundation.tcv.model.LinkInfo; import com.foundation.tcv.swt.*; import com.foundation.tcv.swt.client.cell.CellComponent; import com.foundation.tcv.view.*; import com.foundation.view.LinkData; public abstract class CollectionComponent extends ScrollableComponent implements ICollectionComponent, ICellContainer { /** The only invalid value for an object identifier. */ protected static final int INVALID_OBJECT_ID = -1; /** Used by the hidden data to identify when the previous value has not yet been assigned a value. */ protected static final Object UNSET_VALUE = new Object(); /** Whether the user can type a custom selection. If this is true and the selection attribute is not a String type then the user input must match a list item's text. */ private boolean allowUserItems = false; /** Whether the user is allowed to make multiple selections. */ private boolean allowMultiSelection = false; /** Whether the selection should be automatically sent to the model. */ private boolean autoSynchronizeSelection = false; /** The number of milliseconds of delay before auto synchronizing the selection. */ private long autoSynchronizeSelectionDelay = 500; /** The task that auto synchronizes the selection after a short delay. */ protected Task autoSynchronizeSelectionTask = null; /** Whether to send the double click events to the server for processing. */ private boolean sendDoubleClick = false; /** The collection of HiddenData instances, one for each hidden data holder. */ private IList hiddenDataSets = new LiteList(10, 20); /** Maps the row objects by their unique object identifiers. */ private IntObjectHashMap rowObjectByObjectIdMap = new IntObjectHashMap(100, 200); /** The linkage for the selection related value. */ private Linkage selectionLinkage = new Linkage(); /** The collection of cell components. */ private LiteList cellComponents = null; /** The count of calls to preChangeCollection(). Incremented for each call to preChangeCollection(), decremented for each call to postChangeCollection(). This counter determines when to call internalPostChangeCollection(). */ private int preChangeCollectionCounter = 0; /** * The base class for the different types of hidden data. * Hidden data is used by the collection to activate linkages without going through the controllers or models. * Each object in the collection will then be assigned a value for each hidden data and the selection determines the value assigned to the linkages. */ protected class HiddenData { /** The zero based index of the hidden data. This identifies the hidden data that generates events. */ private int hiddenDataIndex; /** The previous selection related value for this hidden data. This allows linkages to be activated only when the value is altered. */ private Object previousValue = UNSET_VALUE; /** The linkage for the selection related value. */ private Linkage linkage = new Linkage(); /** The multi-selection options for the hidden data. */ private int multiSelectionOption = MULTI_SELECTION_OPTION_DEFAULT; /** The default value for the hidden data. */ private Object defaultValue = null; /** * HiddenData constructor. * @param hiddenDataIndex The index in the hidden data set. */ protected HiddenData(int hiddenDataIndex) { this.hiddenDataIndex = hiddenDataIndex; }//HiddenData()// /** * Adds a link associated with the collection's selection's hidden data value. * @param link The local linkage for the selection related data. */ public void addLink(LinkData link) { linkage.add(link); }//addLink()// /** * Invokes the linkage with the given value. * @param value The last value for the selection and this hidden data. */ public void invokeLinkage(RowObject[] selections) { Object value = null; if((selections == null) || (selections.length == 0) || (multiSelectionOption == MULTI_SELECTION_OPTION_DEFAULT)) { value = defaultValue; }//if// else { value = selections[0].hiddenData[hiddenDataIndex]; for(int index = 1; index < selections.length; index++) { Object next = selections[index].hiddenData[hiddenDataIndex]; switch(multiSelectionOption) { case MULTI_SELECTION_OPTION_COMMON: { //If the values are not comparable then use the default value for the linkage and exit the loop.// if(!Comparator.equals(next, value)) { value = defaultValue; index = selections.length; }//if// break; }//case// case MULTI_SELECTION_OPTION_AND: { if((next instanceof Boolean) && (value instanceof Boolean)) { value = ((Boolean) next).booleanValue() && ((Boolean) value).booleanValue() ? Boolean.TRUE : Boolean.FALSE; }//if// //TODO: What should we do if they are not booleans? For now we will ignore this since a view building tool would prevent the possibility. break; }//case// case MULTI_SELECTION_OPTION_OR: { if((next instanceof Boolean) && (value instanceof Boolean)) { value = ((Boolean) next).booleanValue() || ((Boolean) value).booleanValue() ? Boolean.TRUE : Boolean.FALSE; }//if// //TODO: What should we do if they are not booleans? For now we will ignore this since a view building tool would prevent the possibility. break; }//case// }//switch// }//for// }//else// if(!Comparator.equals(value, previousValue)) { previousValue = value; linkage.invoke(value); }//if// }//invokeLinkage()// /** * Invokes the linkage with the given value. * @param value The last value for the selection and this hidden data. */ public void invokeLinkage(RowObject selection) { Object value = selection != null ? selection.hiddenData[hiddenDataIndex] : defaultValue; if(!Comparator.equals(value, previousValue)) { previousValue = value; linkage.invoke(value); }//if// }//invokeLinkage()// /** * Gets the index for the hidden data. * @return The zero based index identifying the hidden data. */ public int getHiddenDataIndex() { return hiddenDataIndex; }//getHiddenDataIndex()// }//HiddenData// /** * CollectionComponent constructor. */ public CollectionComponent() { super(); }//CollectionComponent()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.ICellContainer#addCellComponent(com.foundation.tcv.swt.client.cell.CellComponent) */ public void addCellComponent(CellComponent component) { if(cellComponents == null) { cellComponents = new LiteList(10, 20); }//if// cellComponents.add(component); if(isInitialized()) { ((AbstractComponent) component).internalViewInitializeAll(); }//if// }//addCellComponent()// /** * Gets the collection of cell components for this container. * @return The cell components contained within this container. */ public IList getCellComponents() { return cellComponents == null ? LiteList.EMPTY_LIST : cellComponents; }//getCellComponents()// /** * Gets the SWT composite that represents this collection component. * @return The SWT composite providing visualization for this collection component. */ public org.eclipse.swt.widgets.Composite getSwtComposite() { return (org.eclipse.swt.widgets.Composite) getSwtWidget(); }//getSwtComposite()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.ICellContainer#getSwtComposite(com.foundation.tcv.swt.client.RowObject) */ public org.eclipse.swt.widgets.Composite getSwtComposite(RowObject rowObject) { return getSwtComposite(); }//getSwtComposite()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.Component#internalViewInitializeAll() */ protected void internalViewInitializeAll() { super.internalViewInitializeAll(); if(cellComponents != null) { for(int index = 0; index < cellComponents.getSize(); index++) { ((AbstractComponent) cellComponents.get(index)).internalViewInitializeAll(); }//for// }//if// }//internalViewInitializeAll()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.Component#internalViewReleaseAll() */ protected void internalViewReleaseAll() { super.internalViewReleaseAll(); if(cellComponents != null) { for(int index = 0; index < cellComponents.getSize(); index++) { ((AbstractComponent) cellComponents.get(index)).internalViewReleaseAll(); }//for// }//if// }//internalViewReleaseAll()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.Component#internalViewSynchronizeAll() */ protected void internalViewSynchronizeAll() { super.internalViewSynchronizeAll(); if(cellComponents != null) { for(int index = 0; index < cellComponents.getSize(); index++) { ((AbstractComponent) cellComponents.get(index)).internalViewSynchronizeAll(); }//for// }//if// }//internalViewSynchronizeAll()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.AbstractComponent#internalViewSynchronize() */ protected void internalViewSynchronize() { super.internalViewSynchronize(); internalViewSynchronizeSelection(); }//internalViewSynchronize()// /** * Synchronizes the selection when requested by the view controller. */ protected void internalViewSynchronizeSelection() { if(!getAutoSynchronizeSelection()) { synchronizeSelection(); }//if// else { synchronized(this) { if(autoSynchronizeSelectionTask != null) { removeTask(autoSynchronizeSelectionTask); autoSynchronizeSelectionTask.execute(); }//if// }//synchronized// }//else// }//internalViewSynchronizeSelection()// /** * Synchronizes the selection when the user interacts with the collection. */ protected abstract void synchronizeSelection(); /* (non-Javadoc) * @see com.foundation.tcv.swt.client.Component#internalViewInitialize() */ protected void internalViewInitialize() { super.internalViewInitialize(); }//internalViewInitialize()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.AbstractComponent#internalViewRelease() */ protected void internalViewRelease() { synchronized(this) { if(autoSynchronizeSelectionTask != null) { removeTask(autoSynchronizeSelectionTask); autoSynchronizeSelectionTask = null; }//if// }//synchronized// super.internalViewRelease(); }//internalViewRelease()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.AbstractComponent#internalProcessMessage(com.foundation.tcv.model.ViewMessage) */ public Object internalProcessMessage(ViewMessage viewMessage) { Object result = null; switch(viewMessage.getMessageNumber()) { case MESSAGE_SET_AUTO_SYNCHRONIZE_SELECTION: { autoSynchronizeSelection = ((Boolean) viewMessage.getMessageData()).booleanValue(); break; }//case// case MESSAGE_SET_AUTO_SYNCHRONIZE_SELECTION_DELAY: { autoSynchronizeSelectionDelay = ((Long) viewMessage.getMessageData()).longValue(); break; }//case// case MESSAGE_SEND_DOUBLE_CLICK: { sendDoubleClick = ((Boolean) viewMessage.getMessageData()).booleanValue(); break; }//case// case MESSAGE_ADD_HIDDEN_DATA: { hiddenDataSets.add(createHiddenData(hiddenDataSets.getSize())); break; }//case// case MESSAGE_ADD_HIDDEN_DATA_LINK: { HiddenData hiddenData = (HiddenData) hiddenDataSets.get(viewMessage.getMessageInteger()); LinkInfo info = (LinkInfo) viewMessage.getMessageData(); hiddenData.linkage.add(new LinkData(getComponent(info.getComponent()), info.getTarget(), info.getData(), info.isBoolean(), info.invertLogic(), info.nullValue())); break; }//case// case MESSAGE_UPDATE_HIDDEN_DATA_DEFAULT_VALUE: { HiddenData hiddenData = (HiddenData) hiddenDataSets.get(viewMessage.getMessageInteger()); Object defaultValue = viewMessage.getMessageData(); hiddenData.defaultValue = defaultValue; if(isInitialized()) { updateSelectionLinks(); }//if// break; }//case// case MESSAGE_UPDATE_HIDDEN_DATA: { Object[] data = (Object[]) viewMessage.getMessageData(); int objectId = ((Integer) data[0]).intValue(); int hiddenDataIndex = ((Integer) data[1]).intValue(); Object hiddenData = data[2]; updateHiddenData(getRowObject(objectId), hiddenDataIndex, hiddenData); break; }//case// case MESSAGE_ADD_SELECTION_LINK: { LinkInfo info = (LinkInfo) viewMessage.getMessageData(); selectionLinkage.add(new LinkData(getComponent(info.getComponent()), info.getTarget(), info.getData(), info.invertLogic())); break; }//case// case MESSAGE_PRE_CHANGE_COLLECTION: { preChangeCollection(); break; }//case// case MESSAGE_POST_CHANGE_COLLECTION: { postChangeCollection(); break; }//case// default: { result = super.internalProcessMessage(viewMessage); }//default// }//switch// return result; }//internalProcessMessage()// /** * Determines whether the control allows the user to type in custom items (combobox). * @return Whether users will be allowed to type in a custom value. */ protected boolean getAllowUserItems() { return allowUserItems; }//getAllowUserItems()// /** * Determines whether the control allows the user to type in custom items (combobox). * @param allowUserItems Whether users will be allowed to type in a custom value. */ protected void setAllowUserItems(boolean allowUserItems) { this.allowUserItems = allowUserItems; }//setAllowUserItems()// /** * Determines whether the control should automatically synchronize all selections. * @return Whether selections should automatically get sent to the server. */ protected boolean getAutoSynchronizeSelection() { return autoSynchronizeSelection; }//getAutoSynchronizeSelection()// /** * Gets the delay in milliseconds between the selection event and updating the model. * @return The number of milliseconds to wait before updating the model. */ protected long getAutoSynchronizeSelectionDelay() { return autoSynchronizeSelectionDelay; }//getAutoSynchronizeSelectionDelay()// /** * Sets the delay in milliseconds between the selection event and updating the model. * @param autoSynchronizeSelectionDelay The number of milliseconds to wait before updating the model. */ protected void setAutoSynchronizeSelectionDelay(long autoSynchronizeSelectionDelay) { this.autoSynchronizeSelectionDelay = autoSynchronizeSelectionDelay; }//setAutoSynchronizeSelectionDelay()// /** * Gets the linkage for the selection state. * @return The linkage used to notify listeners when there is or is not a selection. */ protected Linkage getSelectionLinkage() { return selectionLinkage; }//getSelectionLinkage()// /** * Determines whether the control allows the user to make multiple selections at one time. * @return Whether more than one value can be selected at a time. */ protected boolean getAllowMultiSelection() { return allowMultiSelection; }//getAllowMultiSelection()// /** * Determines whether the control allows the user to make multiple selections at one time. * @param allowMultiSelection Whether more than one value can be selected at a time. */ protected void setAllowMultiSelection(boolean allowMultiSelection) { this.allowMultiSelection = allowMultiSelection; }//setAllowMultiSelection()// /** * Determines whether the control should send double click events to the server. * @return Whether double click events need to be forwarded to the server for processing. */ protected boolean getSendDoubleClick() { return sendDoubleClick; }//getSendDoubleClick()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.ICellContainer#getSwtComposites() */ public IIterator getSwtComposites() { return LiteList.EMPTY_LIST.iterator(); }//getSwtComposites()// /* (non-Javadoc) * @see com.foundation.tcv.swt.client.ICellContainer#setLayout(com.foundation.tcv.swt.client.Layout) */ public void setLayout(Layout layout) { //Not supported.// }//setLayout()// /** * Determines whether the two texts require swapping. * This is used by the sorting code to determine whether rows should be swapped. * @param text1 The first text. * @param text2 The second text (following the first text). * @param reverse Whether the logic should be reversed. * @return Whether text1 and text2 require swapping. */ protected boolean requiresSwap(String text1, String text2, boolean reverse) { int compare = Comparator.getNumericStringComparator().compare(text1, text2); return reverse ? compare < 0 : compare > 0; }//requiresSwap()// /** * Creates a new hidden data object at the given index in the collection. * This can be overloaded to use subclasses which can hold the data associated with the hidden data and the rows of data in the component. * @param hiddenDataIndex The index at for the hidden data which will serve to identify it on the server and client. */ protected HiddenData createHiddenData(int hiddenDataIndex) { return new HiddenData(hiddenDataIndex); }//createHiddenData()// /** * Gets the hidden data set for the given index. * @param hiddenDataIndex The zero based index for the desired hidden data metadata. * @return The metadata describing the hidden data and providing access to the current value for any collection item. */ protected HiddenData getHiddenData(int hiddenDataIndex) { return (HiddenData) hiddenDataSets.get(hiddenDataIndex); }//getHiddenData()// /** * Creates a new row object. * @param objectId The identifier for the object that is represented by the row. * @return An empty row object. */ protected RowObject createRowObject(int objectId) { return new RowObject(objectId, getHiddenDataCount()); }//createRowObject()// /** * Gets the row object containing the data for the object with the given identifier. * @param objectId Identifies the object that the row object represents. * @return The row object that is the avatar for the object identified by the object id. */ public RowObject getRowObject(int objectId) { return (RowObject) rowObjectByObjectIdMap.get(objectId); }//getRowObject()// /** * Adds a new row object to the collection. * @param rowObject The row object to be added. */ protected void addRowObject(RowObject rowObject) { rowObjectByObjectIdMap.put(rowObject.getObjectId(), rowObject); }//addRowObject()// /** * Removes a row object from the collection. * @param rowObject The row object to be removed. */ protected void removeRowObject(int rowObjectId) { ((RowObject) rowObjectByObjectIdMap.remove(rowObjectId)).dispose(); }//removeRowObject()// /** * Removes all row objects from the collection. */ protected void removeRowObjects() { IIterator iterator = rowObjectByObjectIdMap.valueIterator(); while(iterator.hasNext()) { ((RowObject) iterator.next()).dispose(); }//while// rowObjectByObjectIdMap.removeAll(); }//removeRowObjects()// /** * Gets an iterator over all the row objects. * @return The RowObject iterator. */ public IIterator getRowObjects() { return rowObjectByObjectIdMap.valueIterator(); }//getRowObjects()// /** * Gets an iterator over all the row object identifiers. * @return The row object identifier iterator. */ protected IIntIterator getRowObjectIds() { return rowObjectByObjectIdMap.keyIterator(); }//getRowObjectIds()// /** * Gets the number of hidden data sets used by the collection component. * @return The count of hidden data 'columns'. */ protected int getHiddenDataCount() { return hiddenDataSets.getSize(); }//getHiddenDataCount()// /** * Gets the number of selections in the component. * @return The count of selections. */ protected abstract int controlGetSelectionCount(); /** * Updates selection linkages associated with the hidden data 'columns' based on the current selection settings. */ protected void updateSelectionLinks() { selectionLinkage.invoke(controlGetSelectionCount() > 0 ? Boolean.TRUE : Boolean.FALSE); }//updateSelectionLinks()// /** * Sets the hidden data for an object displayed in the collection and the hidden data column. * @param object The row object representing the object whose hidden data is being set. * @param hiddenDataIndex The zero based index of the hidden data column. * @param value The new value for the hidden data for the object. */ protected abstract void updateHiddenData(RowObject object, int hiddenDataIndex, Object value); /** * Called before the collection of displayed values is modified. * This method, in combination with postChangeCollection(), allows the control to be more efficient about its adding of items. */ protected final void preChangeCollection() { if(preChangeCollectionCounter++ == 0) { internalPreChangeCollection(); }//if// }//preChangeCollection()// /** * Called after the collection of displayed values is modified. */ protected final void postChangeCollection() { if(--preChangeCollectionCounter == 0) { internalPostChangeCollection(); }//if// }//postChangeCollection()// /** * Called before the collection of displayed values is modified. * This method, in combination with postChangeCollection(), allows the control to be more efficient about its adding of items. */ protected void internalPreChangeCollection() { //Subclasses should override as required.// }//preChangeCollection()// /** * Called after the collection of displayed values is modified. */ protected void internalPostChangeCollection() { //Subclasses should override as required.// }//postChangeCollection()// }//CollectionComponent//