diff --git a/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle.properties b/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle.properties index 25132548fc..1bd219005a 100644 --- a/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle.properties +++ b/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle.properties @@ -5,6 +5,7 @@ Bundle-Vendor = Andreas Buchen category.name = Portfolio Performance +command.consistencyChecks.name = Check consistency... command.export.name = Export... command.import.name = Import... command.newFile.mnemonic = N diff --git a/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle_de.properties b/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle_de.properties index 5dcfbd8697..6bfc307eaa 100644 --- a/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle_de.properties +++ b/name.abuchen.portfolio.ui/OSGI-INF/l10n/bundle_de.properties @@ -5,6 +5,7 @@ Bundle-Vendor = Andreas Buchen category.name = Portfolio Performance +command.consistencyChecks.name = Auf Konsistenz pr\u00FCfen... command.export.name = Exportieren... command.import.name = Importieren... command.newFile.mnemonic = N diff --git a/name.abuchen.portfolio.ui/icons/check.gif b/name.abuchen.portfolio.ui/icons/check.gif new file mode 100644 index 0000000000..d86b18c167 Binary files /dev/null and b/name.abuchen.portfolio.ui/icons/check.gif differ diff --git a/name.abuchen.portfolio.ui/icons/quickfix.gif b/name.abuchen.portfolio.ui/icons/quickfix.gif new file mode 100644 index 0000000000..542dddca3f Binary files /dev/null and b/name.abuchen.portfolio.ui/icons/quickfix.gif differ diff --git a/name.abuchen.portfolio.ui/plugin.xml b/name.abuchen.portfolio.ui/plugin.xml index 99540f57c3..167fec0546 100644 --- a/name.abuchen.portfolio.ui/plugin.xml +++ b/name.abuchen.portfolio.ui/plugin.xml @@ -111,6 +111,14 @@ id="org.eclipse.ui.file.save" commandId="org.eclipse.ui.file.saveAs"> + + + + @@ -242,6 +250,11 @@ id="name.abuchen.portfolio.ui.commands.updateCommand" name="%command.update.name"> + + + + + + + + + issues = Checker.runAll(client); + + if (issues.isEmpty()) + { + if (reportSuccess) + { + Display.getDefault().asyncExec(new Runnable() + { + @Override + public void run() + { + MessageDialog.openInformation(Display.getCurrent().getActiveShell(), Messages.LabelInfo, + Messages.MsgNoIssuesFound); + } + }); + } + } + else + { + Display.getDefault().asyncExec(new Runnable() + { + @Override + public void run() + { + SelectQuickFixDialog dialog = new SelectQuickFixDialog(Display.getCurrent().getActiveShell(), + issues); + dialog.open(); + + for (ReportedIssue issue : dialog.issues) + { + if (issue.isFixed()) + { + editor.markDirty(); + break; + } + } + } + }); + + } + + return Status.OK_STATUS; + } + + private static class SelectQuickFixDialog extends TitleAreaDialog + { + private final List issues; + private Menu contextMenu; + private TableViewer tableViewer; + + private ReportedIssue currentIssue; + + public SelectQuickFixDialog(Shell shell, List issues) + { + super(shell); + + this.issues = new ArrayList(); + for (Issue issue : issues) + this.issues.add(new ReportedIssue(issue)); + } + + @Override + protected void setShellStyle(int newShellStyle) + { + super.setShellStyle(newShellStyle | SWT.RESIZE); + } + + protected void createButtonsForButtonBar(Composite parent) + { + createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + } + + @Override + protected Control createContents(Composite parent) + { + Control contents = super.createContents(parent); + setTitle(Messages.DialogConsistencyChecksTitle); + setMessage(Messages.DialogConssitencyChecksMessage); + return contents; + } + + @Override + protected Control createDialogArea(Composite parent) + { + Composite composite = (Composite) super.createDialogArea(parent); + + composite.addDisposeListener(new DisposeListener() + { + @Override + public void widgetDisposed(DisposeEvent e) + { + SelectQuickFixDialog.this.widgetDisposed(e); + } + }); + + Composite tableArea = new Composite(composite, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, true).applyTo(tableArea); + tableArea.setLayout(new FillLayout()); + + TableColumnLayout layout = new TableColumnLayout(); + tableArea.setLayout(layout); + + tableViewer = new TableViewer(tableArea, SWT.BORDER | SWT.FULL_SELECTION); + final Table table = tableViewer.getTable(); + table.setHeaderVisible(true); + table.setLinesVisible(true); + + TableViewerColumn col = new TableViewerColumn(tableViewer, SWT.NONE); + col.getColumn().setText(Messages.ColumnDate); + col.setLabelProvider(new ColumnLabelProvider() + { + @Override + public String getText(Object element) + { + return Values.Date.format(((ReportedIssue) element).getDate()); + } + }); + layout.setColumnData(col.getColumn(), new ColumnPixelData(80)); + ColumnViewerSorter.create(ReportedIssue.class, "date").attachTo(tableViewer, col, true); //$NON-NLS-1$ + + col = new TableViewerColumn(tableViewer, SWT.NONE); + col.getColumn().setText(Messages.ColumnEntity); + col.setLabelProvider(new ColumnLabelProvider() + { + @Override + public String getText(Object element) + { + return ((ReportedIssue) element).getEntity().toString(); + } + + @Override + public Image getImage(Object element) + { + ReportedIssue issue = (ReportedIssue) element; + if (issue.getEntity() instanceof Account) + return PortfolioPlugin.image(PortfolioPlugin.IMG_ACCOUNT); + else if (issue.getEntity() instanceof Portfolio) + return PortfolioPlugin.image(PortfolioPlugin.IMG_PORTFOLIO); + else + return null; + } + }); + layout.setColumnData(col.getColumn(), new ColumnPixelData(100)); + ColumnViewerSorter.create(ReportedIssue.class, "entity").attachTo(tableViewer, col); //$NON-NLS-1$ + + col = new TableViewerColumn(tableViewer, SWT.RIGHT); + col.getColumn().setText(Messages.ColumnAmount); + col.setLabelProvider(new ColumnLabelProvider() + { + @Override + public String getText(Object element) + { + Long amount = ((ReportedIssue) element).getAmount(); + return amount != null ? Values.Amount.format(amount) : null; + } + }); + layout.setColumnData(col.getColumn(), new ColumnPixelData(80)); + ColumnViewerSorter.create(ReportedIssue.class, "amount").attachTo(tableViewer, col); //$NON-NLS-1$ + + col = new TableViewerColumn(tableViewer, SWT.NONE); + col.getColumn().setText(Messages.ColumnIssue); + col.setLabelProvider(new OwnerDrawLabelProvider() + { + protected void measure(Event event, Object element) + { + ReportedIssue line = (ReportedIssue) element; + Point size = event.gc.textExtent(line.getLabel()); + event.width = table.getColumn(event.index).getWidth(); + event.width = size.x + 1; + event.height = size.y; + } + + protected void paint(Event event, Object element) + { + ReportedIssue entry = (ReportedIssue) element; + event.gc.drawText(entry.getLabel(), event.x, event.y, true); + } + }); + layout.setColumnData(col.getColumn(), new ColumnPixelData(300)); + ColumnViewerSorter.create(ReportedIssue.class, "label").attachTo(tableViewer, col); //$NON-NLS-1$ + + col = new TableViewerColumn(tableViewer, SWT.NONE); + col.getColumn().setText(Messages.ColumnFix); + col.setLabelProvider(new ColumnLabelProvider() + { + @Override + public String getText(Object element) + { + ReportedIssue issue = (ReportedIssue) element; + return issue.isFixed() ? issue.getFixedMessage() : null; + } + + @Override + public Image getImage(Object element) + { + ReportedIssue issue = (ReportedIssue) element; + + return PortfolioPlugin.image(issue.isFixed() ? PortfolioPlugin.IMG_CHECK + : PortfolioPlugin.IMG_QUICKFIX); + } + }); + layout.setColumnData(col.getColumn(), new ColumnPixelData(100)); + + table.addMouseListener(new MouseAdapter() + { + public void mouseDown(MouseEvent e) + { + Point point = new Point(e.x, e.y); + TableItem item = table.getItem(point); + if (item != null && item.getBounds(4).contains(point)) + { + ReportedIssue issue = (ReportedIssue) item.getData(); + if (!issue.isFixed()) + showContextMenu(issue); + } + } + }); + + tableViewer.setContentProvider(new ArrayContentProvider()); + tableViewer.setInput(issues); + + return composite; + } + + protected void widgetDisposed(DisposeEvent e) + { + if (contextMenu != null) + contextMenu.dispose(); + } + + private void showContextMenu(ReportedIssue issue) + { + this.currentIssue = issue; + if (contextMenu == null) + { + MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$ + menuMgr.setRemoveAllWhenShown(true); + menuMgr.addMenuListener(new IMenuListener() + { + @Override + public void menuAboutToShow(IMenuManager manager) + { + for (final QuickFix fix : currentIssue.getAvailableFixes()) + { + manager.add(new Action(fix.getLabel()) + { + @Override + public void run() + { + fix.execute(); + currentIssue.setFixedMessage(fix.getDoneLabel()); + tableViewer.refresh(currentIssue); + } + }); + } + } + }); + + contextMenu = menuMgr.createContextMenu(getShell()); + } + + contextMenu.setVisible(true); + } + } + + public static class ReportedIssue + { + private Issue delegate; + private List fixes; + private String fixedMessage; + + public ReportedIssue(Issue delegate) + { + this.delegate = delegate; + this.fixes = delegate.getAvailableFixes(); + } + + public Date getDate() + { + return delegate.getDate(); + } + + public Object getEntity() + { + return delegate.getEntity(); + } + + public Long getAmount() + { + return delegate.getAmount(); + } + + public String getLabel() + { + return delegate.getLabel(); + } + + public List getAvailableFixes() + { + return fixes; + } + + public void setFixedMessage(String message) + { + this.fixedMessage = message; + } + + protected String getFixedMessage() + { + return fixedMessage; + } + + public boolean isFixed() + { + return fixedMessage != null; + } + } +} diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Messages.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Messages.java index d27c3e0419..6035f5a015 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Messages.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Messages.java @@ -39,12 +39,15 @@ public class Messages extends NLS public static String ColumnDelta; public static String ColumnDeltaPercent; public static String ColumnDeltaValue; + public static String ColumnEntity; public static String ColumnFees; + public static String ColumnFix; public static String ColumnIndex; public static String ColumnIRR; public static String ColumnIRRPerformance; public static String ColumnIRRPerformanceOption; public static String ColumnISIN; + public static String ColumnIssue; public static String ColumnLable; public static String ColumnLastTrade; public static String ColumnLatest; @@ -104,6 +107,8 @@ public class Messages extends NLS public static String CSVImportWizardDescription; public static String CSVImportWizardTitle; public static String CurrencyConverter_MsgNotANumber; + public static String DialogConsistencyChecksTitle; + public static String DialogConssitencyChecksMessage; public static String DialogTitleCounterTransactionNotFound; public static String DialogTitleMultipleCounterTransactions; public static String EditWizardIndustryClassificationDescription; @@ -150,6 +155,7 @@ public class Messages extends NLS public static String JobMsgErrorUpdatingIndeces; public static String JobMsgErrorUpdatingQuotes; public static String JobMsgLoadingExchanges; + public static String JobMsgRunningConsistencyChecks; public static String JobMsgSamplingHistoricalQuotes; public static String LabelAccounts; public static String LabelAggregationDaily; @@ -231,6 +237,7 @@ public class Messages extends NLS public static String MsgMissingAccount; public static String MsgMissingReferenceAccount; public static String MsgMissingSecurity; + public static String MsgNoIssuesFound; public static String MsgNoProfileFound; public static String MsgNoSecuritiesMaintained; public static String MsgNoUpdatesAvailable; diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/PortfolioPlugin.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/PortfolioPlugin.java index 4e007e49df..ff4902193b 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/PortfolioPlugin.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/PortfolioPlugin.java @@ -88,6 +88,9 @@ public interface Preferences public static final String IMG_VIEW_TREEMAP = "view_treemap"; //$NON-NLS-1$ public static final String IMG_VIEW_PIECHART = "view_piechart"; //$NON-NLS-1$ + public static final String IMG_CHECK = "check"; //$NON-NLS-1$ + public static final String IMG_QUICKFIX = "quickfix"; //$NON-NLS-1$ + private static PortfolioPlugin instance; public PortfolioPlugin() @@ -116,7 +119,8 @@ protected void initializeImageRegistry(ImageRegistry registry) Bundle bundle = Platform.getBundle(PLUGIN_ID); for (String key : new String[] { IMG_LOGO, IMG_ACCOUNT, IMG_PORTFOLIO, IMG_SECURITY, IMG_WATCHLIST, IMG_PLUS, - IMG_CONFIG, IMG_EXPORT, IMG_VIEW_TABLE, IMG_VIEW_TREEMAP, IMG_VIEW_PIECHART }) + IMG_CONFIG, IMG_EXPORT, IMG_VIEW_TABLE, IMG_VIEW_TREEMAP, IMG_VIEW_PIECHART, IMG_CHECK, + IMG_QUICKFIX }) { IPath path = new Path("icons/" + key + ".gif"); //$NON-NLS-1$ //$NON-NLS-2$ URL url = FileLocator.find(bundle, path, null); @@ -139,7 +143,7 @@ public static void log(Throwable t) { log(new Status(Status.ERROR, PLUGIN_ID, t.getMessage(), t)); } - + public static void log(ArrayList errors) { for (Exception e : errors) diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/handlers/RunConsistencyChecksHandler.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/handlers/RunConsistencyChecksHandler.java new file mode 100644 index 0000000000..6a83adff4c --- /dev/null +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/handlers/RunConsistencyChecksHandler.java @@ -0,0 +1,31 @@ +package name.abuchen.portfolio.ui.handlers; + +import name.abuchen.portfolio.model.Client; +import name.abuchen.portfolio.ui.ClientEditor; +import name.abuchen.portfolio.ui.ConsistencyChecksJob; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.handlers.HandlerUtil; + +public class RunConsistencyChecksHandler extends AbstractHandler +{ + public Object execute(ExecutionEvent event) throws ExecutionException + { + IWorkbenchPage page = HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); + final IEditorPart activeEditor = page.getActiveEditor(); + + if (!(activeEditor instanceof ClientEditor)) + return null; + + ClientEditor editor = (ClientEditor) activeEditor; + final Client client = editor.getClient(); + + new ConsistencyChecksJob(editor, client, true).schedule(); + + return null; + } +} diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages.properties index 2ed5393be5..fa4feb1c9c 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages.properties @@ -103,8 +103,12 @@ ColumnDeltaPercent = Delta % ColumnDeltaValue = Delta Value +ColumnEntity = Entity + ColumnFees = Fees +ColumnFix = Fix + ColumnIRR = IRR ColumnIRRPerformance = IRR Performance @@ -115,6 +119,8 @@ ColumnISIN = ISIN ColumnIndex = Index +ColumnIssue = Issue + ColumnLable = Label ColumnLastTrade = Last Trade @@ -197,6 +203,10 @@ ConsumerPriceIndexMenuDelete = Delete CurrencyConverter_MsgNotANumber = Not a valid number %s +DialogConsistencyChecksTitle = Results of the consistency checks + +DialogConssitencyChecksMessage = Resolve a reported issue by clicking into the last column and select one of the proposed fixes.\nRe-run the consistency check via the main menu. + DialogTitleCounterTransactionNotFound = Counter transaction not found DialogTitleMultipleCounterTransactions = Multiple potential counter transactions found @@ -289,6 +299,8 @@ JobMsgErrorUpdatingQuotes = Errors while updating quotes JobMsgLoadingExchanges = Loading exchanges +JobMsgRunningConsistencyChecks = Running consistency checks + JobMsgSamplingHistoricalQuotes = Sampling historical quotes from exchange {0} LabelAccounts = Accounts @@ -451,6 +463,8 @@ MsgMissingReferenceAccount = Reference account of portfolio missing MsgMissingSecurity = Security is missing +MsgNoIssuesFound = No issues found. + MsgNoProfileFound = No installation profile found. Running from IDE? MsgNoSecuritiesMaintained = Transactions about securities can only be entered if a security has been created in the section "Securities" -> "All securities" beforehand. diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_de.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_de.properties index 00e56a6d09..cb681163eb 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_de.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_de.properties @@ -103,8 +103,12 @@ ColumnDeltaPercent = Delta % ColumnDeltaValue = Delta Wert +ColumnEntity = Objekt + ColumnFees = Geb\u00FChren +ColumnFix = Fix + ColumnIRR = IZF ColumnIRRPerformance = IZF Performance @@ -115,6 +119,8 @@ ColumnISIN = ISIN ColumnIndex = Index +ColumnIssue = Problem + ColumnLable = Bezeichnung ColumnLastTrade = Letzter Handel @@ -197,6 +203,10 @@ ConsumerPriceIndexMenuDelete = L\u00F6schen CurrencyConverter_MsgNotANumber = Keine g\u00FCltige Zahl %s +DialogConsistencyChecksTitle = Ergebnis der Konsistenzpr\u00FCfung + +DialogConssitencyChecksMessage = Mit einem Klick in die letzte Spalte kann ein Problem durch eine der vorgeschlagenen L\u00F6sung behoben werden.\nDie Konsistenzpr\u00FCfung kann jederzeit \u00FCber das Men\u00FC erneut ausgef\u00FChrt werden. + DialogTitleCounterTransactionNotFound = Gegenbuchung nicht gefunden DialogTitleMultipleCounterTransactions = Mehrere potenzielle Gegenbuchungen gefunden diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/ColumnViewerSorter.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/ColumnViewerSorter.java index f9c1b14b99..85f01eb576 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/ColumnViewerSorter.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/ColumnViewerSorter.java @@ -76,7 +76,9 @@ private BeanComparator(Class clazz, String attribute) Class returnType = method.getReturnType(); - if (returnType.isAssignableFrom(String.class)) + if (returnType.equals(Object.class)) + type = 0; + else if (returnType.isAssignableFrom(String.class)) type = 1; else if (returnType.isAssignableFrom(Enum.class)) type = 2; @@ -148,8 +150,7 @@ public static ColumnViewerSorter create(Class clazz, String... attributes) for (String attribute : attributes) comparators.add(new BeanComparator(clazz, attribute)); - return new ColumnViewerSorter(comparators.size() == 1 ? comparators.get(0) : new ChainedComparator( - comparators)); + return new ColumnViewerSorter(comparators.size() == 1 ? comparators.get(0) : new ChainedComparator(comparators)); } public static ColumnViewerSorter create(Comparator comparator)