001: /**
002: *
003: */package net.sf.jmoney.entrytable;
004:
005: import java.util.HashMap;
006: import java.util.Map;
007:
008: import net.sf.jmoney.model2.Commodity;
009: import net.sf.jmoney.model2.Currency;
010: import net.sf.jmoney.model2.Entry;
011: import net.sf.jmoney.model2.ExtendableObject;
012: import net.sf.jmoney.model2.SessionChangeAdapter;
013: import net.sf.jmoney.model2.Transaction;
014:
015: import org.eclipse.swt.SWT;
016: import org.eclipse.swt.events.SelectionAdapter;
017: import org.eclipse.swt.events.SelectionEvent;
018: import org.eclipse.swt.events.ShellAdapter;
019: import org.eclipse.swt.events.ShellEvent;
020: import org.eclipse.swt.events.ShellListener;
021: import org.eclipse.swt.graphics.Rectangle;
022: import org.eclipse.swt.layout.GridData;
023: import org.eclipse.swt.layout.GridLayout;
024: import org.eclipse.swt.layout.RowLayout;
025: import org.eclipse.swt.widgets.Button;
026: import org.eclipse.swt.widgets.Composite;
027: import org.eclipse.swt.widgets.Control;
028: import org.eclipse.swt.widgets.Display;
029: import org.eclipse.swt.widgets.Shell;
030:
031: /**
032: * This class implements the shell that drops down to show the split entries.
033: *
034: * @author Nigel Westbury
035: */
036: public class OtherEntriesShell {
037:
038: private Shell parentShell;
039:
040: private EntryData entryData;
041:
042: private Block<Entry, SplitEntryRowControl> rootBlock;
043:
044: private Shell shell;
045:
046: private RowSelectionTracker<SplitEntryRowControl> rowTracker = new RowSelectionTracker<SplitEntryRowControl>();
047:
048: private Map<Entry, SplitEntryRowControl> rowControls = new HashMap<Entry, SplitEntryRowControl>();
049:
050: public OtherEntriesShell(Shell parent, int style,
051: EntryData entryData,
052: Block<Entry, SplitEntryRowControl> rootBlock,
053: boolean isLinked) {
054: shell = new Shell(parent, style | SWT.MODELESS);
055:
056: this .parentShell = parent;
057: this .entryData = entryData;
058: this .rootBlock = rootBlock;
059:
060: GridLayout layout = new GridLayout(1, false);
061: layout.marginWidth = 0;
062: layout.marginHeight = 0;
063: layout.verticalSpacing = 3;
064: shell.setLayout(layout);
065:
066: Control entriesTable = createEntriesTable(shell, isLinked);
067: entriesTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL,
068: true, false));
069:
070: Control buttonArea = createButtonArea(shell);
071: buttonArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
072: false));
073:
074: shell.pack();
075: }
076:
077: private Control createEntriesTable(Composite parent,
078: final boolean isLinked) {
079: final Composite composite = new Composite(parent, SWT.NONE);
080:
081: GridLayout layout = new GridLayout(1, false);
082: layout.marginWidth = 0;
083: layout.marginHeight = 0;
084: layout.horizontalSpacing = 0;
085: layout.verticalSpacing = 0;
086: composite.setLayout(layout);
087:
088: /*
089: * Use a single row tracker and cell focus tracker for this table.
090: */
091: final FocusCellTracker cellTracker = new FocusCellTracker();
092: for (Entry entry : entryData.getSplitEntries()) {
093: SplitEntryRowControl row = new SplitEntryRowControl(
094: composite, SWT.NONE, rootBlock, isLinked,
095: rowTracker, cellTracker);
096: row.setContent(entry);
097: rowControls.put(entry, row);
098: }
099:
100: entryData.getEntry().getDataManager().addChangeListener(
101: new SessionChangeAdapter() {
102: @Override
103: public void objectInserted(
104: ExtendableObject newObject) {
105: if (newObject instanceof Entry) {
106: Entry newEntry = (Entry) newObject;
107: if (newEntry.getTransaction() == entryData
108: .getEntry().getTransaction()) {
109: entryData.getSplitEntries().add(
110: newEntry);
111: SplitEntryRowControl row = new SplitEntryRowControl(
112: composite, SWT.NONE, rootBlock,
113: isLinked, rowTracker,
114: cellTracker);
115: row.setContent(newEntry);
116: rowControls.put(newEntry, row);
117: shell.pack();
118: }
119: }
120: }
121:
122: @Override
123: public void objectRemoved(
124: ExtendableObject deletedObject) {
125: if (deletedObject instanceof Entry) {
126: Entry deletedEntry = (Entry) deletedObject;
127: if (deletedEntry.getTransaction() == entryData
128: .getEntry().getTransaction()) {
129: entryData.getSplitEntries().remove(
130: deletedEntry);
131: rowControls.get(deletedEntry).dispose();
132: rowControls.remove(deletedEntry);
133: shell.pack();
134: }
135: }
136: }
137: }, composite);
138:
139: return composite;
140: }
141:
142: private Control createButtonArea(Composite parent) {
143: Composite composite = new Composite(parent, SWT.NONE);
144:
145: RowLayout layout = new RowLayout(SWT.HORIZONTAL);
146: layout.marginBottom = 0;
147: layout.marginTop = 0;
148: composite.setLayout(layout);
149:
150: Button newSplitButton = new Button(composite, SWT.PUSH);
151: newSplitButton.setText("New Split");
152: newSplitButton.addSelectionListener(new SelectionAdapter() {
153: @Override
154: public void widgetSelected(SelectionEvent e) {
155: addSplit();
156: }
157: });
158:
159: Button deleteSplitButton = new Button(composite, SWT.PUSH);
160: deleteSplitButton.setText("Delete Split");
161: deleteSplitButton.addSelectionListener(new SelectionAdapter() {
162: @Override
163: public void widgetSelected(SelectionEvent e) {
164: deleteSplit();
165: }
166: });
167:
168: Button adjustButton = new Button(composite, SWT.PUSH);
169: adjustButton.setText("Adjust");
170: adjustButton
171: .setToolTipText("Adjust the amount of the selected split to balance the transaction");
172: adjustButton.addSelectionListener(new SelectionAdapter() {
173: @Override
174: public void widgetSelected(SelectionEvent e) {
175: adjustAmount();
176: }
177: });
178:
179: return composite;
180: }
181:
182: private void addSplit() {
183: // entryData.getEntry().getTransaction().createEntry();
184:
185: Transaction transaction = entryData.getEntry().getTransaction();
186: Entry newEntry = transaction.createEntry();
187:
188: long total = 0;
189: Commodity commodity = null;
190: for (Entry entry : transaction.getEntryCollection()) {
191: if (entry.getCommodity() != null) {
192: if (commodity == null) {
193: commodity = entry.getCommodity();
194: } else if (!commodity.equals(entry.getCommodity())) {
195: // We have entries with mismatching commodities set.
196: // This means there is an exchange of one commodity
197: // for another so we do not expect the total amount
198: // of all the entries to be zero. Leave the amount
199: // for this new entry blank (a zero amount).
200: total = 0;
201: break;
202: }
203: }
204:
205: total += entry.getAmount();
206: }
207:
208: newEntry.setAmount(-total);
209:
210: // We set the currency by default to be the currency
211: // of the top-level entry.
212:
213: // The currency of an entry is not
214: // applicable if the entry is an entry in a currency account
215: // (because all entries in a currency account must have the
216: // currency of the account). However, we set it anyway so
217: // the value is there if the entry is set to an income and
218: // expense account (which would cause the currency property
219: // to become applicable).
220:
221: // It may be that the currency of the top-level entry is not known.
222: // This is not possible if entries in a currency account
223: // are being listed, but may be possible if this entries list
224: // control is used for more general purposes. In this case,
225: // the currency is not set and so the user must enter it.
226: if (entryData.getEntry().getCommodity() instanceof Currency) {
227: newEntry.setIncomeExpenseCurrency((Currency) entryData
228: .getEntry().getCommodity());
229: }
230:
231: // Select the new entry in the entries list.
232: //??? setSelection(selectedEntry, newEntry);
233: }
234:
235: private void deleteSplit() {
236: SplitEntryRowControl rowControl = rowTracker.getSelectedRow();
237: // TODO: Is a row ever not selected?
238: if (rowControl != null) {
239: Entry entry = rowControl.getContent();
240: entryData.getEntry().getTransaction().deleteEntry(entry);
241: }
242: }
243:
244: private void adjustAmount() {
245: SplitEntryRowControl rowControl = rowTracker.getSelectedRow();
246: // TODO: Is a row ever not selected?
247: if (rowControl != null) {
248: Entry entry = rowControl.getContent();
249:
250: long totalAmount = 0;
251: for (Entry eachEntry : entryData.getEntry()
252: .getTransaction().getEntryCollection()) {
253: totalAmount += eachEntry.getAmount();
254: }
255:
256: entry.setAmount(entry.getAmount() - totalAmount);
257: }
258: }
259:
260: public void open(Rectangle rect) {
261: /*
262: * Position the split-entries shell below the given rectangle, unless
263: * this control is so near the bottom of the display that the
264: * shell would go off the bottom of the display, in
265: * which case position the split-entries shell above this
266: * control.
267: *
268: * In either case, the shell should overlap the rectangle, so if it
269: * is going downwards, align the top with the top of this control.
270: *
271: * Note also that we put the shell one pixel to the left. This is because
272: * a single pixel margin is always added to BlockLayout so that the
273: * selection line can be drawn. We want the controls in the shell to
274: * exactly line up with the table header.
275: */
276:
277: Display display = shell.getDisplay();
278: int shellHeight = shell.getSize().y;
279: if (rect.y + rect.height + shellHeight <= display.getBounds().height) {
280: shell.setLocation(rect.x - 1, rect.y);
281: } else {
282: shell.setLocation(rect.x - 1, rect.y + rect.height
283: - shellHeight);
284: }
285:
286: shell.open();
287:
288: /*
289: * We need to be sure to close the shell when it is no longer active.
290: * Listening for this shell to be deactivated does not work because there
291: * may be child controls which create child shells (third level shells).
292: * We do not want this shell to close if a child shell has been created
293: * and activated. We want to close this shell only if the parent shell
294: * have been activated. Note that if a grandparent shell is activated then
295: * we do not want to close this shell. The parent will be closed anyway
296: * which would automatically close this one.
297: */
298: final ShellListener parentActivationListener = new ShellAdapter() {
299: @Override
300: public void shellActivated(ShellEvent e) {
301: shell.close();
302: }
303: };
304:
305: parentShell.addShellListener(parentActivationListener);
306:
307: shell.addShellListener(new ShellAdapter() {
308: @Override
309: public void shellClosed(ShellEvent e) {
310: parentShell
311: .removeShellListener(parentActivationListener);
312: }
313: });
314: }
315: }
|