package name.abuchen.portfolio.ui.views;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import name.abuchen.portfolio.model.BuySellEntry;
import name.abuchen.portfolio.model.Portfolio;
import name.abuchen.portfolio.model.PortfolioTransaction;
import name.abuchen.portfolio.model.PortfolioTransferEntry;
import name.abuchen.portfolio.model.Transaction;
import name.abuchen.portfolio.model.TransactionPair;
import name.abuchen.portfolio.money.Values;
import name.abuchen.portfolio.ui.AbstractFinanceView;
import name.abuchen.portfolio.ui.Images;
import name.abuchen.portfolio.ui.Messages;
import name.abuchen.portfolio.ui.dialogs.transactions.OpenDialogAction;
import name.abuchen.portfolio.ui.dialogs.transactions.SecurityTransactionDialog;
import name.abuchen.portfolio.ui.dialogs.transactions.SecurityTransferDialog;
import name.abuchen.portfolio.ui.util.BookmarkMenu;
import name.abuchen.portfolio.ui.util.Colors;
import name.abuchen.portfolio.ui.util.viewers.Column;
import name.abuchen.portfolio.ui.util.viewers.ColumnEditingSupport;
import name.abuchen.portfolio.ui.util.viewers.ColumnEditingSupport.ModificationListener;
import name.abuchen.portfolio.ui.util.viewers.ColumnViewerSorter;
import name.abuchen.portfolio.ui.util.viewers.DateEditingSupport;
import name.abuchen.portfolio.ui.util.viewers.SharesLabelProvider;
import name.abuchen.portfolio.ui.util.viewers.ShowHideColumnHelper;
import name.abuchen.portfolio.ui.util.viewers.StringEditingSupport;
import name.abuchen.portfolio.ui.util.viewers.ValueEditingSupport;
import name.abuchen.portfolio.ui.views.actions.ConvertBuySellToDeliveryAction;
import name.abuchen.portfolio.ui.views.actions.ConvertDeliveryToBuySellAction;
public final class PortfolioTransactionsViewer implements ModificationListener
{
private class TransactionLabelProvider extends ColumnLabelProvider
{
private Color warningColor = new Color(Display.getDefault(), Colors.WARNING.swt());
@Override
public Color getForeground(Object element)
{
if (marked.contains(element))
return Display.getDefault().getSystemColor(SWT.COLOR_BLACK);
PortfolioTransaction t = (PortfolioTransaction) element;
return Display.getCurrent()
.getSystemColor(t.getType().isLiquidation() ? SWT.COLOR_DARK_RED : SWT.COLOR_DARK_GREEN);
}
@Override
public Color getBackground(Object element)
{
return marked.contains(element) ? warningColor : null;
}
@Override
public void dispose()
{
warningColor.dispose();
super.dispose();
}
}
private AbstractFinanceView owner;
private Portfolio portfolio;
private Set<PortfolioTransaction> marked = new HashSet<>();
private TableViewer tableViewer;
private ShowHideColumnHelper support;
private boolean fullContextMenu = true;
private Menu contextMenu;
public PortfolioTransactionsViewer(Composite parent, AbstractFinanceView owner)
{
this.owner = owner;
Composite container = new Composite(parent, SWT.NONE);
TableColumnLayout layout = new TableColumnLayout();
container.setLayout(layout);
tableViewer = new TableViewer(container, SWT.FULL_SELECTION | SWT.MULTI);
ColumnViewerToolTipSupport.enableFor(tableViewer, ToolTip.NO_RECREATE);
ColumnEditingSupport.prepare(tableViewer);
support = new ShowHideColumnHelper(PortfolioTransactionsViewer.class.getSimpleName() + "3", //$NON-NLS-1$
owner.getPreferenceStore(), tableViewer, layout);
addColumns();
support.createColumns();
tableViewer.getTable().setHeaderVisible(true);
tableViewer.getTable().setLinesVisible(true);
tableViewer.setContentProvider(ArrayContentProvider.getInstance());
hookContextMenu(parent);
hookKeyListener();
}
public void setFullContextMenu(boolean fullContextMenu)
{
this.fullContextMenu = fullContextMenu;
}
public Control getControl()
{
return tableViewer.getControl().getParent();
}
public void markTransactions(List<PortfolioTransaction> transactions)
{
marked.addAll(transactions);
}
public void setInput(Portfolio portfolio, List<PortfolioTransaction> transactions)
{
this.portfolio = portfolio;
this.tableViewer.setInput(transactions);
}
public void refresh()
{
tableViewer.refresh();
}
public Portfolio getPortfolio()
{
return portfolio;
}
@Override
public void onModified(Object element, Object newValue, Object oldValue)
{
PortfolioTransaction t = (PortfolioTransaction) element;
if (t.getCrossEntry() != null)
t.getCrossEntry().updateFrom(t);
owner.markDirty();
owner.notifyModelUpdated();
}
private void addColumns()
{
Column column = new Column(Messages.ColumnDate, SWT.None, 80);
column.setLabelProvider(new TransactionLabelProvider()
{
@Override
public String getText(Object element)
{
return Values.Date.format(((PortfolioTransaction) element).getDate());
}
});
ColumnViewerSorter.create(PortfolioTransaction.class, "date").attachTo(column, SWT.DOWN); //$NON-NLS-1$
new DateEditingSupport(PortfolioTransaction.class, "date").addListener(this).attachTo(column); //$NON-NLS-1$
support.addColumn(column);
column = new Column(Messages.ColumnTransactionType, SWT.None, 80);
column.setLabelProvider(new TransactionLabelProvider()
{
@Override
public String getText(Object element)
{
return ((PortfolioTransaction) element).getType().toString();
}
});
ColumnViewerSorter.create(PortfolioTransaction.class, "type").attachTo(column); //$NON-NLS-1$
support.addColumn(column);
column = new Column(Messages.ColumnSecurity, SWT.None, 250);
column.setLabelProvider(new TransactionLabelProvider()
{
@Override
public String getText(Object element)
{
PortfolioTransaction t = (PortfolioTransaction) element;
return t.getSecurity() != null ? String.valueOf(t.getSecurity()) : null;
}
});
ColumnViewerSorter.create(PortfolioTransaction.class, "security").attachTo(column); //$NON-NLS-1$
support.addColumn(column);
column = new Column(Messages.ColumnShares, SWT.RIGHT, 80);
column.setLabelProvider(new SharesLabelProvider()
{
private TransactionLabelProvider colors = new TransactionLabelProvider();
@Override
public Long getValue(Object element)
{
return ((PortfolioTransaction) element).getShares();
}
@Override
public Color getForeground(Object element)
{
return colors.getForeground(element);
}
@Override
public Color getBackground(Object element)
{
return colors.getBackground(element);
}
});
ColumnViewerSorter.create(PortfolioTransaction.class, "shares").attachTo(column); //$NON-NLS-1$
new ValueEditingSupport(PortfolioTransaction.class, "shares", Values.Share).addListener(this).attachTo(column); //$NON-NLS-1$
support.addColumn(column);
column = new Column(Messages.ColumnQuote, SWT.RIGHT, 80);
column.setLabelProvider(new TransactionLabelProvider()
{
@Override
public String getText(Object element)
{
PortfolioTransaction t = (PortfolioTransaction) element;
return t.getShares() != 0
? Values.Quote.format(t.getGrossPricePerShare(), owner.getClient().getBaseCurrency())
: null;
}
});
ColumnViewerSorter.create(PortfolioTransaction.class, "grossPricePerShare").attachTo(column); //$NON-NLS-1$
support.addColumn(column);
column = new Column(Messages.ColumnAmount, SWT.RIGHT, 80);
column.setLabelProvider(new TransactionLabelProvider()
{
@Override
public String getText(Object element)
{
return Values.Money.format(((PortfolioTransaction) element).getGrossValue(),
owner.getClient().getBaseCurrency());
}
});
ColumnViewerSorter.create(PortfolioTransaction.class, "grossValueAmount").attachTo(column); //$NON-NLS-1$
support.addColumn(column);
column = new Column(Messages.ColumnFees, SWT.RIGHT, 80);
column.setLabelProvider(new TransactionLabelProvider()
{
@Override
public String getText(Object element)
{
PortfolioTransaction t = (PortfolioTransaction) element;
return Values.Money.format(t.getUnitSum(Transaction.Unit.Type.FEE),
owner.getClient().getBaseCurrency());
}
});
support.addColumn(column);
column = new Column(Messages.ColumnTaxes, SWT.RIGHT, 80);
column.setLabelProvider(new TransactionLabelProvider()
{
@Override
public String getText(Object element)
{
PortfolioTransaction t = (PortfolioTransaction) element;
return Values.Money.format(t.getUnitSum(Transaction.Unit.Type.TAX),
owner.getClient().getBaseCurrency());
}
});
support.addColumn(column);
column = new Column(Messages.ColumnNetValue, SWT.RIGHT, 80);
column.setLabelProvider(new TransactionLabelProvider()
{
@Override
public String getText(Object element)
{
PortfolioTransaction t = (PortfolioTransaction) element;
return Values.Money.format(t.getMonetaryAmount(), owner.getClient().getBaseCurrency());
}
});
ColumnViewerSorter.create(PortfolioTransaction.class, "amount").attachTo(column); //$NON-NLS-1$
support.addColumn(column);
column = new Column(Messages.ColumnOffsetAccount, SWT.None, 120);
column.setLabelProvider(new TransactionLabelProvider()
{
@Override
public String getText(Object e)
{
PortfolioTransaction t = (PortfolioTransaction) e;
return t.getCrossEntry() != null ? t.getCrossEntry().getCrossOwner(t).toString() : null;
}
});
support.addColumn(column);
column = new Column(Messages.ColumnNote, SWT.None, 200);
column.setLabelProvider(new TransactionLabelProvider()
{
@Override
public String getText(Object e)
{
return ((PortfolioTransaction) e).getNote();
}
@Override
public Image getImage(Object e)
{
String note = ((PortfolioTransaction) e).getNote();
return note != null && note.length() > 0 ? Images.NOTE.image() : null;
}
});
ColumnViewerSorter.create(PortfolioTransaction.class, "note").attachTo(column); //$NON-NLS-1$
new StringEditingSupport(PortfolioTransaction.class, "note").addListener(this).attachTo(column); //$NON-NLS-1$
support.addColumn(column);
}
public ShowHideColumnHelper getColumnSupport()
{
return support;
}
private void hookContextMenu(Composite parent)
{
MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(this::fillTransactionsContextMenu);
contextMenu = menuMgr.createContextMenu(parent.getShell());
tableViewer.getTable().setMenu(contextMenu);
tableViewer.getTable().addDisposeListener(e -> PortfolioTransactionsViewer.this.widgetDisposed());
}
private void hookKeyListener()
{
tableViewer.getControl().addKeyListener(new KeyAdapter()
{
@Override
public void keyPressed(KeyEvent e)
{
if (e.keyCode == 'e' && e.stateMask == SWT.MOD1)
{
PortfolioTransaction transaction = (PortfolioTransaction) ((IStructuredSelection) tableViewer
.getSelection()).getFirstElement();
if (transaction != null)
createEditAction(transaction).run();
}
}
});
}
private void widgetDisposed()
{
if (contextMenu != null)
contextMenu.dispose();
}
private void fillTransactionsContextMenu(IMenuManager manager)
{
if (portfolio == null)
return;
IStructuredSelection selection = (IStructuredSelection) tableViewer.getSelection();
PortfolioTransaction firstTransaction = (PortfolioTransaction) selection.getFirstElement();
if (firstTransaction != null && selection.size() == 1)
{
Action editAction = createEditAction(firstTransaction);
editAction.setAccelerator(SWT.MOD1 | 'E');
manager.add(editAction);
manager.add(new Separator());
if (fullContextMenu && (firstTransaction.getType() == PortfolioTransaction.Type.BUY
|| firstTransaction.getType() == PortfolioTransaction.Type.SELL))
{
manager.add(new ConvertBuySellToDeliveryAction(owner.getClient(),
new TransactionPair<>(portfolio, firstTransaction)));
manager.add(new Separator());
}
if (fullContextMenu && (firstTransaction.getType() == PortfolioTransaction.Type.DELIVERY_INBOUND
|| firstTransaction.getType() == PortfolioTransaction.Type.DELIVERY_OUTBOUND))
{
manager.add(new ConvertDeliveryToBuySellAction(owner.getClient(),
new TransactionPair<>(portfolio, firstTransaction)));
manager.add(new Separator());
}
if (fullContextMenu)
new SecurityContextMenu(owner).menuAboutToShow(manager, firstTransaction.getSecurity(), portfolio);
else
manager.add(new BookmarkMenu(owner.getPart(), firstTransaction.getSecurity()));
}
else if (firstTransaction == null)
{
new SecurityContextMenu(owner).menuAboutToShow(manager, null, portfolio);
}
if (firstTransaction != null)
{
manager.add(new Separator());
manager.add(new Action(Messages.MenuTransactionDelete)
{
@Override
public void run()
{
Object[] transactions = selection.toArray();
for (Object transaction : transactions)
portfolio.deleteTransaction((PortfolioTransaction) transaction, owner.getClient());
owner.markDirty();
owner.notifyModelUpdated();
}
});
}
}
private Action createEditAction(PortfolioTransaction transaction)
{
// buy / sell
if (transaction.getCrossEntry() instanceof BuySellEntry)
{
BuySellEntry entry = (BuySellEntry) transaction.getCrossEntry();
return new OpenDialogAction(this.owner, Messages.MenuEditTransaction)
.type(SecurityTransactionDialog.class, d -> d.setBuySellEntry(entry))
.parameters(entry.getPortfolioTransaction().getType());
}
else if (transaction.getCrossEntry() instanceof PortfolioTransferEntry)
{
PortfolioTransferEntry entry = (PortfolioTransferEntry) transaction.getCrossEntry();
return new OpenDialogAction(this.owner, Messages.MenuEditTransaction) //
.type(SecurityTransferDialog.class, d -> d.setEntry(entry));
}
else
{
TransactionPair<PortfolioTransaction> pair = new TransactionPair<>(portfolio, transaction);
return new OpenDialogAction(this.owner, Messages.MenuEditTransaction) //
.type(SecurityTransactionDialog.class, d -> d.setDeliveryTransaction(pair)) //
.parameters(transaction.getType());
}
}
}