package name.abuchen.portfolio.ui.wizards.security; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.ComboViewer; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import name.abuchen.portfolio.model.Exchange; import name.abuchen.portfolio.model.Security; import name.abuchen.portfolio.online.QuoteFeed; import name.abuchen.portfolio.ui.Messages; import name.abuchen.portfolio.ui.PortfolioPlugin; public abstract class AbstractQuoteProviderPage extends AbstractPage { private class LoadExchangesJob extends Job { public LoadExchangesJob() { super(Messages.JobMsgLoadingExchanges); setSystem(true); } @Override public IStatus run(IProgressMonitor monitor) { List<QuoteFeed> provider = getAvailableFeeds(); monitor.beginTask(Messages.JobMsgLoadingExchanges, provider.size()); for (QuoteFeed feed : provider) { Security s = buildTemporarySecurity(); List<Exception> errors = new ArrayList<Exception>(); cacheExchanges.put(feed, feed.getExchanges(s, errors)); PortfolioPlugin.log(errors); monitor.worked(1); } Display.getDefault().asyncExec(new Runnable() { @Override public void run() { QuoteFeed feed = (QuoteFeed) ((IStructuredSelection) comboProvider.getSelection()) .getFirstElement(); if (feed != null && feed.getId() != null) { List<Exchange> exchanges = cacheExchanges.get(feed); if (comboExchange != null) { comboExchange.setSelection(StructuredSelection.EMPTY); if (exchanges != null) { comboExchange.setInput(exchanges); // if ticker symbol matches any of the // exchanges, select this exchange in the // combo list exchanges.stream() // .filter(e -> e.getId().equals(model.getTickerSymbol())) // .findAny() // .ifPresent(e -> comboExchange.setSelection(new StructuredSelection(e))); } if (comboExchange.getSelection().isEmpty()) clearSampleQuotes(); else showSampleQuotes(feed, (Exchange) ((StructuredSelection) comboExchange.getSelection()) .getFirstElement()); } else { if (exchanges == null || exchanges.isEmpty()) { showSampleQuotes(feed, null); } } } } }); monitor.done(); return Status.OK_STATUS; } } private static final String YAHOO = "YAHOO"; //$NON-NLS-1$ private static final String HTML = "HTML"; //$NON-NLS-1$ private ComboViewer comboProvider; private Group grpQuoteFeed; private Label labelDetailData; private ComboViewer comboExchange; private Text textFeedURL; private final EditSecurityModel model; // used to identify if the ticker has been changed on another page private String tickerSymbol; private Map<QuoteFeed, List<Exchange>> cacheExchanges = new HashMap<QuoteFeed, List<Exchange>>(); protected AbstractQuoteProviderPage(EditSecurityModel model) { this.model = model; } protected final EditSecurityModel getModel() { return model; } protected abstract String getFeed(); protected abstract void setFeed(String feed); protected abstract String getFeedURL(); protected abstract void setFeedURL(String feedURL); protected abstract void setStatus(String status); protected abstract void createSampleArea(Composite parent); protected abstract List<QuoteFeed> getAvailableFeeds(); protected abstract QuoteFeed getQuoteFeedProvider(String feedId); protected abstract void reinitCaches(); protected abstract void clearSampleQuotes(); protected abstract void showSampleQuotes(QuoteFeed feed, Exchange exchange); @Override public void beforePage() { if (!Objects.equals(tickerSymbol, model.getTickerSymbol())) { this.tickerSymbol = model.getTickerSymbol(); // clear caches cacheExchanges = new HashMap<QuoteFeed, List<Exchange>>(); reinitCaches(); new LoadExchangesJob().schedule(); QuoteFeed feed = (QuoteFeed) ((IStructuredSelection) comboProvider.getSelection()).getFirstElement(); if (feed.getId() != null && feed.getId().indexOf(HTML) >= 0) { if (getFeedURL() == null || getFeedURL().length() == 0) clearSampleQuotes(); else showSampleQuotes(feed, null); } } } @Override public void afterPage() { QuoteFeed feed = (QuoteFeed) ((IStructuredSelection) comboProvider.getSelection()).getFirstElement(); setFeed(feed.getId()); if (comboExchange != null && feed.getId() != null && feed.getId().startsWith(YAHOO)) { Exchange exchange = (Exchange) ((IStructuredSelection) comboExchange.getSelection()).getFirstElement(); if (exchange != null) { model.setTickerSymbol(exchange.getId()); tickerSymbol = exchange.getId(); setFeedURL(null); } } else if (textFeedURL != null) { setFeedURL(textFeedURL.getText()); } } @Override public final void createControl(Composite parent) { Composite container = new Composite(parent, SWT.NONE); setControl(container); container.setLayout(new FormLayout()); createProviderGroup(container); Composite sampleArea = new Composite(container, SWT.NONE); sampleArea.setLayout(new FillLayout()); createSampleArea(sampleArea); FormData data = new FormData(); data.top = new FormAttachment(grpQuoteFeed, 5); data.left = new FormAttachment(0, 10); data.right = new FormAttachment(100, -10); data.bottom = new FormAttachment(100, -10); sampleArea.setLayoutData(data); setupInitialData(); comboProvider.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { onFeedProviderChanged(event); } }); } /** * Builds a temporary {@link Security} from the currently selected values. * * @return {@link Security} */ protected Security buildTemporarySecurity() { // create a temporary security and set all attributes Security security = new Security(); model.setAttributes(security); return security; } private void createProviderGroup(Composite container) { grpQuoteFeed = new Group(container, SWT.NONE); grpQuoteFeed.setText(Messages.LabelQuoteFeed); FormData fd_grpQuoteFeed = new FormData(); fd_grpQuoteFeed.top = new FormAttachment(0, 5); fd_grpQuoteFeed.left = new FormAttachment(0, 10); grpQuoteFeed.setLayoutData(fd_grpQuoteFeed); GridLayoutFactory.fillDefaults().numColumns(2).margins(5, 5).applyTo(grpQuoteFeed); Label lblProvider = new Label(grpQuoteFeed, SWT.NONE); lblProvider.setText(Messages.LabelQuoteFeedProvider); comboProvider = new ComboViewer(grpQuoteFeed, SWT.READ_ONLY); comboProvider.setContentProvider(ArrayContentProvider.getInstance()); comboProvider.setLabelProvider(new LabelProvider() { @Override public String getText(Object element) { return ((QuoteFeed) element).getName(); } }); comboProvider.setInput(getAvailableFeeds()); GridDataFactory.fillDefaults().hint(300, SWT.DEFAULT).applyTo(comboProvider.getControl()); labelDetailData = new Label(grpQuoteFeed, SWT.NONE); GridDataFactory.fillDefaults().indent(0, 5).applyTo(labelDetailData); createDetailDataWidgets(null); } private void createDetailDataWidgets(QuoteFeed feed) { boolean dropDown = feed != null && feed.getId() != null && feed.getId().startsWith(YAHOO); boolean feedURL = feed != null && feed.getId() != null && feed.getId().indexOf(HTML) >= 0; if (dropDown) { labelDetailData.setText(Messages.LabelExchange); if (textFeedURL != null) { textFeedURL.dispose(); textFeedURL = null; } if (comboExchange == null) { comboExchange = new ComboViewer(grpQuoteFeed, SWT.READ_ONLY); comboExchange.setContentProvider(ArrayContentProvider.getInstance()); comboExchange.setLabelProvider(new LabelProvider() { @Override public String getText(Object element) { return ((Exchange) element).getName(); } }); GridDataFactory.fillDefaults().hint(300, SWT.DEFAULT).applyTo(comboExchange.getControl()); comboExchange.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { onExchangeChanged(event); } }); } } else if (feedURL) { labelDetailData.setText(Messages.EditWizardQuoteFeedLabelFeedURL); if (comboExchange != null) { comboExchange.getControl().dispose(); comboExchange = null; } if (textFeedURL == null) { textFeedURL = new Text(grpQuoteFeed, SWT.BORDER); GridDataFactory.fillDefaults().hint(300, SWT.DEFAULT).applyTo(textFeedURL); textFeedURL.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { onFeedURLChanged(); } }); } } else { labelDetailData.setText(""); //$NON-NLS-1$ if (comboExchange != null) { comboExchange.getControl().dispose(); comboExchange = null; } if (textFeedURL != null) { textFeedURL.dispose(); textFeedURL = null; } } grpQuoteFeed.layout(true); grpQuoteFeed.getParent().layout(); } private void setupInitialData() { QuoteFeed feed = getQuoteFeedProvider(getFeed()); if (feed != null) comboProvider.setSelection(new StructuredSelection(feed)); else comboProvider.getCombo().select(0); createDetailDataWidgets(feed); if (model.getTickerSymbol() != null && feed != null && feed.getId() != null && feed.getId().startsWith("YAHOO")) //$NON-NLS-1$ { Exchange exchange = new Exchange(model.getTickerSymbol(), model.getTickerSymbol()); ArrayList<Exchange> input = new ArrayList<Exchange>(); input.add(exchange); comboExchange.setInput(input); comboExchange.setSelection(new StructuredSelection(exchange)); } else if (textFeedURL != null) { textFeedURL.setText(getFeedURL()); } } private void onFeedProviderChanged(SelectionChangedEvent event) { String previousExchangeId = null; if (comboExchange != null) { Exchange exchange = (Exchange) ((IStructuredSelection) comboExchange.getSelection()).getFirstElement(); if (exchange != null) previousExchangeId = exchange.getId(); } if (previousExchangeId == null && model.getTickerSymbol() != null) { previousExchangeId = model.getTickerSymbol(); } QuoteFeed feed = (QuoteFeed) ((IStructuredSelection) event.getSelection()).getFirstElement(); createDetailDataWidgets(feed); if (comboExchange != null) { List<Exchange> exchanges = cacheExchanges.get(feed); comboExchange.setInput(exchanges); // select exchange if other provider supports same exchange id // (yahoo close vs. yahoo adjusted close) boolean exchangeSelected = false; if (exchanges != null && previousExchangeId != null) { for (Exchange e : exchanges) { if (e.getId().equals(previousExchangeId)) { comboExchange.setSelection(new StructuredSelection(e)); exchangeSelected = true; break; } } } if (!exchangeSelected) comboExchange.setSelection(null); setStatus(exchangeSelected ? null : MessageFormat.format(Messages.MsgErrorExchangeMissing, getTitle())); } else if (textFeedURL != null) { boolean hasURL = getFeedURL() != null && getFeedURL().length() > 0; if (hasURL) textFeedURL.setText(getFeedURL()); setStatus(hasURL ? null : MessageFormat.format(Messages.EditWizardQuoteFeedMsgErrorMissingURL, getTitle())); } else { // get sample quotes? if (feed != null) { showSampleQuotes(feed, null); } else { clearSampleQuotes(); } setStatus(null); } } private void onExchangeChanged(SelectionChangedEvent event) { Exchange exchange = (Exchange) ((IStructuredSelection) event.getSelection()).getFirstElement(); setStatus(null); if (exchange == null) { clearSampleQuotes(); } else { QuoteFeed feed = (QuoteFeed) ((IStructuredSelection) comboProvider.getSelection()).getFirstElement(); showSampleQuotes(feed, exchange); } } private void onFeedURLChanged() { setFeedURL(textFeedURL.getText()); boolean hasURL = getFeedURL() != null && getFeedURL().length() > 0; if (!hasURL) { clearSampleQuotes(); setStatus(MessageFormat.format(Messages.EditWizardQuoteFeedMsgErrorMissingURL, getTitle())); } else { QuoteFeed feed = (QuoteFeed) ((IStructuredSelection) comboProvider.getSelection()).getFirstElement(); showSampleQuotes(feed, null); setStatus(null); } } }