package name.abuchen.portfolio.ui; import java.util.List; import java.util.UUID; 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.dialogs.InputDialog; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; 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 com.ibm.icu.text.MessageFormat; import name.abuchen.portfolio.model.Classification; import name.abuchen.portfolio.model.Client; import name.abuchen.portfolio.model.Security; import name.abuchen.portfolio.model.Taxonomy; import name.abuchen.portfolio.model.TaxonomyTemplate; import name.abuchen.portfolio.model.Watchlist; import name.abuchen.portfolio.ui.Sidebar.Entry; import name.abuchen.portfolio.ui.dnd.SecurityTransfer; import name.abuchen.portfolio.ui.util.LabelOnly; /* package */class ClientEditorSidebar { private final class ActivateViewAction extends Action { private String view; private Object parameter; private ActivateViewAction(String label, String view) { this(label, view, null, null); } private ActivateViewAction(String text, String view, ImageDescriptor image) { this(text, view, null, image); } public ActivateViewAction(String text, String view, Object parameter, ImageDescriptor image) { super(text, image); this.view = view; this.parameter = parameter; } @Override public void run() { editor.activateView(view, parameter); } } private static final String START_PAGE_KEY = "pp-start-page"; //$NON-NLS-1$ private PortfolioPart editor; private Menu taxonomyMenu; private ScrolledComposite scrolledComposite; private Sidebar sidebar; private Entry allSecurities; private Entry statementOfAssets; private Entry taxonomies; private Sidebar.MenuListener setAsStartPage = (entry, manager) -> manager .add(new Action(Messages.MenuLabelSetAsStartPage) { @Override public void run() { editor.getPreferenceStore().setValue(START_PAGE_KEY, entry.getId()); } }); public ClientEditorSidebar(PortfolioPart editor) { this.editor = editor; } public Control createSidebarControl(Composite parent) { scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL); sidebar = new Sidebar(scrolledComposite, SWT.NONE); createGeneralDataSection(sidebar); createMasterDataSection(sidebar); createPerformanceSection(sidebar); createTaxonomyDataSection(sidebar); createMiscSection(sidebar); scrolledComposite.setContent(sidebar); scrolledComposite.setExpandVertical(true); scrolledComposite.setExpandHorizontal(true); parent.getParent().addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { scrolledComposite.setMinSize(sidebar.computeSize(SWT.DEFAULT, SWT.DEFAULT)); } }); return scrolledComposite; } public void selectDefaultView() { String defaultView = editor.getPreferenceStore().getString(START_PAGE_KEY); if (defaultView == null) { sidebar.select(statementOfAssets); } else { Entry entry = sidebar.selectById(defaultView); if (entry == null) sidebar.select(statementOfAssets); } } private void createGeneralDataSection(final Sidebar sidebar) { final Entry section = new Entry(sidebar, Messages.LabelSecurities); section.setAction(new Action(Messages.LabelSecurities, Images.PLUS.descriptor()) { @Override public void run() { String name = askWatchlistName(Messages.WatchlistNewLabel); if (name == null) return; Watchlist watchlist = new Watchlist(); watchlist.setName(name); editor.getClient().getWatchlists().add(watchlist); editor.markDirty(); createWatchlistEntry(section, watchlist); sidebar.layout(); scrolledComposite.setMinSize(sidebar.computeSize(SWT.DEFAULT, SWT.DEFAULT)); } }); allSecurities = new Entry(section, new ActivateViewAction(Messages.LabelAllSecurities, "SecurityList", //$NON-NLS-1$ Images.SECURITY.descriptor())); allSecurities.setContextMenu(setAsStartPage); for (Watchlist watchlist : editor.getClient().getWatchlists()) createWatchlistEntry(section, watchlist); } private void createWatchlistEntry(Entry section, final Watchlist watchlist) { Entry entry = new Entry(section, watchlist.getName()); entry.setAction(new ActivateViewAction(watchlist.getName(), "SecurityList", watchlist, //$NON-NLS-1$ Images.WATCHLIST.descriptor())); entry.setContextMenu((e, m) -> watchlistContextMenuAboutToShow(watchlist, e, m)); entry.addDropSupport(DND.DROP_MOVE, new Transfer[] { SecurityTransfer.getTransfer() }, new DropTargetAdapter() { @Override public void drop(DropTargetEvent event) { if (SecurityTransfer.getTransfer().isSupportedType(event.currentDataType)) { Security security = SecurityTransfer.getTransfer().getSecurity(); if (security != null) { // if the security is dragged from another file, add // a deep copy to the client's securities list if (!editor.getClient().getSecurities().contains(security)) { security = security.deepCopy(); editor.getClient().addSecurity(security); } if (!watchlist.getSecurities().contains(security)) watchlist.addSecurity(security); editor.markDirty(); editor.notifyModelUpdated(); } } } }); } private void watchlistContextMenuAboutToShow(Watchlist watchlist, Entry entry, IMenuManager manager) { manager.add(new Action(Messages.WatchlistRename) { @Override public void run() { String newName = askWatchlistName(watchlist.getName()); if (newName != null) { watchlist.setName(newName); editor.markDirty(); entry.setLabel(newName); } } }); manager.add(new Action(Messages.WatchlistDelete) { @Override public void run() { editor.getClient().getWatchlists().remove(watchlist); editor.markDirty(); entry.dispose(); allSecurities.select(); scrolledComposite.setMinSize(sidebar.computeSize(SWT.DEFAULT, SWT.DEFAULT)); } }); manager.add(new Separator()); addMoveUpAndDownActions(watchlist, entry, manager); manager.add(new Separator()); setAsStartPage.menuAboutToShow(entry, manager); } private void addMoveUpAndDownActions(Watchlist watchlist, Entry entry, IMenuManager manager) { List<Watchlist> list = editor.getClient().getWatchlists(); int size = list.size(); int index = list.indexOf(watchlist); if (index > 0) { manager.add(new Action(Messages.MenuMoveUp) { @Override public void run() { Client client = editor.getClient(); List<Watchlist> watchlists = client.getWatchlists(); watchlists.remove(watchlist); watchlists.add(index - 1, watchlist); client.markDirty(); entry.moveUp(); sidebar.layout(); } }); } if (index < size - 1 && size > 1) { manager.add(new Action(Messages.MenuMoveDown) { @Override public void run() { Client client = editor.getClient(); List<Watchlist> watchlists = client.getWatchlists(); watchlists.remove(watchlist); watchlists.add(index + 1, watchlist); client.markDirty(); entry.findNeighbor(SWT.ARROW_DOWN).moveUp(); sidebar.layout(); } }); } } private String askWatchlistName(String initialValue) { InputDialog dlg = new InputDialog(Display.getDefault().getActiveShell(), Messages.WatchlistEditDialog, Messages.WatchlistEditDialogMsg, initialValue, null); if (dlg.open() != InputDialog.OK) return null; return dlg.getValue(); } private void createMasterDataSection(Sidebar sidebar) { Entry section = new Entry(sidebar, Messages.ClientEditorLabelClientMasterData); new Entry(section, new ActivateViewAction(Messages.LabelAccounts, "AccountList", //$NON-NLS-1$ Images.ACCOUNT.descriptor())).setContextMenu(setAsStartPage); new Entry(section, new ActivateViewAction(Messages.LabelPortfolios, "PortfolioList", //$NON-NLS-1$ Images.PORTFOLIO.descriptor())).setContextMenu(setAsStartPage); new Entry(section, new ActivateViewAction(Messages.LabelInvestmentPlans, "InvestmentPlanList", //$NON-NLS-1$ Images.INVESTMENTPLAN.descriptor())).setContextMenu(setAsStartPage); } private void createPerformanceSection(Sidebar sidebar) { Entry section = new Entry(sidebar, Messages.ClientEditorLabelReports); statementOfAssets = new Entry(section, new ActivateViewAction(Messages.LabelStatementOfAssets, "StatementOfAssets")); //$NON-NLS-1$ statementOfAssets.setContextMenu(setAsStartPage); new Entry(statementOfAssets, new ActivateViewAction(Messages.ClientEditorLabelChart, "StatementOfAssetsHistory")) //$NON-NLS-1$ .setContextMenu(setAsStartPage); new Entry(statementOfAssets, new ActivateViewAction(Messages.ClientEditorLabelHoldings, "HoldingsPieChart")) //$NON-NLS-1$ .setContextMenu(setAsStartPage); Entry performance = new Entry(section, new ActivateViewAction(Messages.ClientEditorLabelPerformance, "dashboard.Dashboard")); //$NON-NLS-1$ performance.setContextMenu(setAsStartPage); new Entry(performance, new ActivateViewAction(Messages.ClientEditorPerformanceCalculation, "Performance")) //$NON-NLS-1$ .setContextMenu(setAsStartPage); new Entry(performance, new ActivateViewAction(Messages.ClientEditorLabelChart, "PerformanceChart")) //$NON-NLS-1$ .setContextMenu(setAsStartPage); new Entry(performance, new ActivateViewAction(Messages.ClientEditorLabelReturnsVolatility, "ReturnsVolatilityChart")) //$NON-NLS-1$ .setContextMenu(setAsStartPage); new Entry(performance, new ActivateViewAction(Messages.LabelSecurities, "SecuritiesPerformance")) //$NON-NLS-1$ .setContextMenu(setAsStartPage); new Entry(performance, new ActivateViewAction(Messages.LabelDividends, "dividends.Dividends")) //$NON-NLS-1$ .setContextMenu(setAsStartPage); } private void createTaxonomyDataSection(final Sidebar sidebar) { taxonomies = new Entry(sidebar, Messages.LabelTaxonomies); taxonomies.setAction(new Action(Messages.LabelTaxonomies, Images.PLUS.descriptor()) { @Override public void run() { showCreateTaxonomyMenu(); } }); for (Taxonomy taxonomy : editor.getClient().getTaxonomies()) createTaxonomyEntry(taxonomies, taxonomy); } private Entry createTaxonomyEntry(Entry section, final Taxonomy taxonomy) { Entry entry = new Entry(section, taxonomy.getName()); entry.setAction(new ActivateViewAction(taxonomy.getName(), "taxonomy.Taxonomy", taxonomy, null)); //$NON-NLS-1$ entry.setContextMenu((e, m) -> taxonomyContextMenuAboutToShow(taxonomy, e, m)); return entry; } private void showCreateTaxonomyMenu() { if (taxonomyMenu == null) { MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$ menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(manager -> taxonomyCreateMenuAboutToShow(manager)); taxonomyMenu = menuMgr.createContextMenu(sidebar.getShell()); sidebar.addDisposeListener(e -> taxonomyMenu.dispose()); } taxonomyMenu.setVisible(true); } private void taxonomyCreateMenuAboutToShow(IMenuManager manager) { manager.add(new Action(Messages.MenuTaxonomyCreate) { @Override public void run() { String name = askTaxonomyName(Messages.LabelNewTaxonomy); if (name == null) return; Taxonomy taxonomy = new Taxonomy(name); taxonomy.setRootNode(new Classification(UUID.randomUUID().toString(), name)); addAndOpenTaxonomy(taxonomy); } }); manager.add(new Separator()); manager.add(new LabelOnly(Messages.LabelTaxonomyTemplates)); for (final TaxonomyTemplate template : TaxonomyTemplate.list()) { manager.add(new Action(template.getName()) { @Override public void run() { addAndOpenTaxonomy(template.build()); } }); } } private void taxonomyContextMenuAboutToShow(Taxonomy taxonomy, Entry entry, IMenuManager manager) { manager.add(new Action(Messages.MenuTaxonomyRename) { @Override public void run() { String newName = askTaxonomyName(taxonomy.getName()); if (newName != null) { taxonomy.setName(newName); editor.markDirty(); entry.setLabel(newName); } } }); manager.add(new Action(Messages.MenuTaxonomyCopy) { @Override public void run() { String newName = askTaxonomyName(MessageFormat.format(Messages.LabelNamePlusCopy, taxonomy.getName())); if (newName != null) { Taxonomy copy = taxonomy.copy(); copy.setName(newName); addAndOpenTaxonomy(copy); } } }); manager.add(new Action(Messages.MenuTaxonomyDelete) { @Override public void run() { editor.getClient().removeTaxonomy(taxonomy); editor.markDirty(); entry.dispose(); statementOfAssets.select(); scrolledComposite.setMinSize(sidebar.computeSize(SWT.DEFAULT, SWT.DEFAULT)); } }); manager.add(new Separator()); addMoveUpAndDownActions(taxonomy, entry, manager); manager.add(new Separator()); setAsStartPage.menuAboutToShow(entry, manager); } private void addMoveUpAndDownActions(Taxonomy taxonomy, Entry entry, IMenuManager manager) { List<Taxonomy> list = editor.getClient().getTaxonomies(); int size = list.size(); int index = list.indexOf(taxonomy); if (index > 0) { manager.add(new Action(Messages.MenuMoveUp) { @Override public void run() { Client client = editor.getClient(); client.removeTaxonomy(taxonomy); client.addTaxonomy(index - 1, taxonomy); client.markDirty(); entry.moveUp(); sidebar.layout(); } }); } if (index < size - 1 && size > 1) { manager.add(new Action(Messages.MenuMoveDown) { @Override public void run() { Client client = editor.getClient(); client.removeTaxonomy(taxonomy); client.addTaxonomy(index + 1, taxonomy); client.markDirty(); entry.findNeighbor(SWT.ARROW_DOWN).moveUp(); sidebar.layout(); } }); } } private void addAndOpenTaxonomy(Taxonomy taxonomy) { editor.getClient().addTaxonomy(taxonomy); editor.markDirty(); Entry entry = createTaxonomyEntry(taxonomies, taxonomy); sidebar.select(entry); sidebar.layout(); scrolledComposite.setMinSize(sidebar.computeSize(SWT.DEFAULT, SWT.DEFAULT)); } private String askTaxonomyName(String initialValue) { InputDialog dlg = new InputDialog(Display.getDefault().getActiveShell(), Messages.DialogTaxonomyNameTitle, Messages.DialogTaxonomyNamePrompt, initialValue, null); if (dlg.open() != InputDialog.OK) return null; return dlg.getValue(); } private void createMiscSection(Sidebar sidebar) { Entry section = new Entry(sidebar, Messages.ClientEditorLabelGeneralData); new Entry(section, new ActivateViewAction(Messages.LabelConsumerPriceIndex, "ConsumerPriceIndexList")) //$NON-NLS-1$ .setContextMenu(setAsStartPage); new Entry(section, new ActivateViewAction(Messages.LabelCurrencies, "ExchangeRatesList")) //$NON-NLS-1$ .setContextMenu(setAsStartPage); new Entry(section, new ActivateViewAction(Messages.LabelSettings, "settings.Settings")) //$NON-NLS-1$ .setContextMenu(setAsStartPage); if ("yes".equals(System.getProperty("name.abuchen.portfolio.debug"))) //$NON-NLS-1$ //$NON-NLS-2$ new Entry(section, new ActivateViewAction("Browser Test", "BrowserTest")); //$NON-NLS-1$ //$NON-NLS-2$ } }