001: package net.sf.jmoney.entrytable;
002:
003: import java.util.HashMap;
004: import java.util.Map;
005:
006: import org.eclipse.swt.SWT;
007: import org.eclipse.swt.events.FocusListener;
008: import org.eclipse.swt.graphics.Color;
009: import org.eclipse.swt.widgets.Composite;
010: import org.eclipse.swt.widgets.Control;
011: import org.eclipse.swt.widgets.Display;
012:
013: public abstract class RowControl<T, R extends RowControl<T, R>> extends
014: Composite {
015:
016: protected static final Color selectedCellColor = new Color(Display
017: .getCurrent(), 255, 255, 255);
018:
019: protected abstract boolean commitChanges();
020:
021: protected abstract void setSelected(boolean isSelected);
022:
023: // Although currently the keys of this map are never used
024: // (and it may as well be a list of the values only), a map
025: // allows us to do stuff like move the focus to the control
026: // in error during transaction validation.
027: protected Map<CellBlock, ICellControl<? super T>> controls = new HashMap<CellBlock, ICellControl<? super T>>();
028:
029: protected RowSelectionTracker<R> selectionTracker;
030: protected FocusCellTracker focusCellTracker;
031:
032: /**
033: * the current input, being always a non-null value if this row
034: * is active and undefined if this row is inactive
035: */
036: protected T input;
037:
038: public RowControl(Composite parent, int style) {
039: super (parent, style);
040:
041: /*
042: * By default the child controls get the same background as
043: * this composite.
044: */
045: setBackgroundMode(SWT.INHERIT_FORCE);
046: }
047:
048: /**
049: * This method must always be called by the constructor of the final derived
050: * classes of this class. Why do we not just call it from the constructor
051: * of this class? The reason is because the controls that are created by this method have a back reference to
052: * this object. These back references are typed (using generics) to the
053: * final derived type. These controls will expect field initializers and
054: * possibly constructor initialization to have been done on the final derived type.
055: * However, at the time the base constructor is called, neither will have
056: * been initialized.
057: *
058: * @param rootBlock
059: * @param selectionTracker
060: * @param focusCellTracker
061: */
062: protected void init(R this RowControl,
063: Block<T, ? super R> rootBlock,
064: RowSelectionTracker<R> selectionTracker,
065: FocusCellTracker focusCellTracker) {
066: this .selectionTracker = selectionTracker;
067: this .focusCellTracker = focusCellTracker;
068:
069: for (CellBlock<? super T, ? super R> cellBlock : rootBlock
070: .buildCellList()) {
071: // Create the control with no content set.
072: createCellControl(this , cellBlock);
073: }
074: }
075:
076: /**
077: * This method creates the controls.
078: *
079: * This method is usually called from init to create the controls. However, in the
080: * case of the StackBlock, controls are created for the top block only which means if
081: * the top block changes then controls may need to be created at a later time. This method
082: * should be called to create the controls in order to ensure that the controls are properly
083: * adapted with the correct listeners and so on.
084: * @param <R>
085: * @param parent
086: * @param thisRowControl
087: * @param selectionTracker
088: * @param focusCellTracker
089: * @param cellBlock
090: */
091: public void createCellControl(Composite parent,
092: CellBlock<? super T, ? super R> cellBlock) {
093: final ICellControl<? super T> cellControl = cellBlock
094: .createCellControl(parent, getThis());
095: controls.put(cellBlock, cellControl);
096:
097: if (input != null) {
098: cellControl.load(input);
099: }
100:
101: FocusListener controlFocusListener = new CellFocusListener<R>(
102: getThis(), cellControl, selectionTracker,
103: focusCellTracker);
104:
105: Control control = cellControl.getControl();
106: // control.addKeyListener(keyListener);
107: addFocusListenerRecursively(control, controlFocusListener);
108: // control.addTraverseListener(traverseListener);
109:
110: // This is needed in case more child controls are created at a
111: // later time. This is not the cleanest code, but the UI for these
112: // split entries may be changed at a later time anyway.
113: cellControl.setFocusListener(controlFocusListener);
114: }
115:
116: protected abstract R getThis();
117:
118: /**
119: * Add listeners to each control.
120: *
121: * @param control The control to listen to.
122: */
123: protected void addFocusListenerRecursively(Control control,
124: FocusListener listener) {
125: control.addFocusListener(listener);
126:
127: if (control instanceof Composite) {
128: Composite composite = (Composite) control;
129: for (int i = 0; i < composite.getChildren().length; i++) {
130: Control childControl = composite.getChildren()[i];
131: addFocusListenerRecursively(childControl, listener);
132: }
133: }
134: }
135:
136: /**
137: * This version is called when the selection changed as a result
138: * of the user clicking on a control in another row. Therefore
139: * we do not set the cell selection.
140: */
141: public void arrive() {
142: setSelected(true);
143: }
144:
145: /**
146: * This method should be called whenever a row is to lose selection.
147: * It makes whatever changes are necessary to the display of the row
148: * and saves if necessary the data in the row.
149: *
150: * @return true if this method succeeded (or failed in some way that
151: * is not the user's fault and that the user cannot correct),
152: * false if this method could not save the data because the user
153: * has not properly entered the data and so selection should remain
154: * on the row (in which case this method will display an appropriate
155: * message to the user)
156: */
157: public boolean canDepart() {
158: if (!commitChanges()) {
159: return false;
160: }
161:
162: setSelected(false);
163: return true;
164: }
165:
166: /**
167: * This method is called when the selected row may not be visible (because the
168: * user scrolled the table) but we want to make it visible again because there
169: * was an error in it and we were unable to move the selection off the row.
170: */
171: protected abstract void scrollToShowRow();
172:
173: }
|