Files
Brainstorm/Foundation TCV SWT Client/src/com/foundation/tcv/swt/client/CollectionComponent.java
2014-05-30 10:31:51 -07:00

558 lines
21 KiB
Java

/*
* 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//