diff --git a/name.abuchen.portfolio.ui/icons/crosshair_off.png b/name.abuchen.portfolio.ui/icons/crosshair_off.png new file mode 100644 index 0000000000..b0802615ab Binary files /dev/null and b/name.abuchen.portfolio.ui/icons/crosshair_off.png differ diff --git a/name.abuchen.portfolio.ui/icons/crosshair_off@2x.png b/name.abuchen.portfolio.ui/icons/crosshair_off@2x.png new file mode 100644 index 0000000000..cc18b0ab15 Binary files /dev/null and b/name.abuchen.portfolio.ui/icons/crosshair_off@2x.png differ diff --git a/name.abuchen.portfolio.ui/icons/crosshair_on.png b/name.abuchen.portfolio.ui/icons/crosshair_on.png new file mode 100644 index 0000000000..5058a2cd2a Binary files /dev/null and b/name.abuchen.portfolio.ui/icons/crosshair_on.png differ diff --git a/name.abuchen.portfolio.ui/icons/crosshair_on@2x.png b/name.abuchen.portfolio.ui/icons/crosshair_on@2x.png new file mode 100644 index 0000000000..b44cee2557 Binary files /dev/null and b/name.abuchen.portfolio.ui/icons/crosshair_on@2x.png differ diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Images.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Images.java index bbba18361d..b933dc85dc 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Images.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Images.java @@ -56,6 +56,8 @@ public enum Images CLOUD("cloud.png"), //$NON-NLS-1$ MEASUREMENT_ON("measurement_on.png"), //$NON-NLS-1$ MEASUREMENT_OFF("measurement_off.png"), //$NON-NLS-1$ + CROSSHAIR_ON("crosshair_on.png"), //$NON-NLS-1$ + CROSSHAIR_OFF("crosshair_off.png"), //$NON-NLS-1$ // views 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 51d5119345..0ef55b72bf 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 @@ -591,6 +591,7 @@ public class Messages extends NLS public static String LabelConvertBuySellIntoDeliveryTransactions; public static String LabelCopyToClipboard; public static String LabelCountry; + public static String LabelCrosshair; public static String LabelCurrencies; public static String LabelCurrencyConverter; public static String LabelCurrentDate; 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 ee9ea09f8f..1ded05f451 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 @@ -1192,6 +1192,8 @@ LabelCopyToClipboard = Copy to Clipboard LabelCountry = Country +LabelCrosshair=Crosshair + LabelCurrencies = Currencies LabelCurrencyConverter = Currency Converter diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_cs.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_cs.properties index fb77442f84..8f557de216 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_cs.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_cs.properties @@ -1186,6 +1186,8 @@ LabelCopyToClipboard = Zkop\u00EDrovat do schr\u00E1nky LabelCountry = Zem\u011B +LabelCrosshair = Crosshair + LabelCurrencies = M\u011Bny LabelCurrencyConverter = P\u0159evodn\u00EDk m\u011Bn diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_da.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_da.properties index 216c445967..7beecd10cf 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_da.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_da.properties @@ -1185,6 +1185,8 @@ LabelCopyToClipboard = Kopier til udklipsholder LabelCountry = Land +LabelCrosshair = Tr\u00E5dkors + LabelCurrencies = Valutaer LabelCurrencyConverter = Valuta 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 99bcc2a055..aba9857ebb 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 @@ -1185,6 +1185,8 @@ LabelCopyToClipboard = In Zwischenablage kopieren LabelCountry = Land +LabelCrosshair = Fadenkreuz + LabelCurrencies = W\u00E4hrungen LabelCurrencyConverter = W\u00E4hrungsrechner diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_es.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_es.properties index d043308345..97a5797fd8 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_es.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_es.properties @@ -1185,6 +1185,8 @@ LabelCopyToClipboard = Copiar al portapapeles LabelCountry = Pa\u00EDs +LabelCrosshair = Crosshair + LabelCurrencies = Monedas LabelCurrencyConverter = Conversor de Moneda diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_fr.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_fr.properties index 1898a47ccf..2f4e2b76f7 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_fr.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_fr.properties @@ -1186,6 +1186,8 @@ LabelCopyToClipboard = Copier dans le presse-papier LabelCountry = Pays +LabelCrosshair = R\u00E9ticule + LabelCurrencies = Devises LabelCurrencyConverter = Convertisseur de devise diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_it.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_it.properties index 83a462cadd..f08f7b7ac4 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_it.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_it.properties @@ -1185,6 +1185,8 @@ LabelCopyToClipboard = Copia negli appunti LabelCountry = Nazione +LabelCrosshair = Mirino + LabelCurrencies = Valute LabelCurrencyConverter = Convertitore valuta diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_nl.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_nl.properties index 244c118aad..09a80157e6 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_nl.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_nl.properties @@ -1185,6 +1185,8 @@ LabelCopyToClipboard = Kopi\u00EBren naar klembord LabelCountry = Land +LabelCrosshair = Dradenkruis + LabelCurrencies = Valuta LabelCurrencyConverter = Valuta Converter diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pl.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pl.properties index f837d21ebc..3e9241a31e 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pl.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pl.properties @@ -1185,6 +1185,8 @@ LabelCopyToClipboard = Skopiuj do schowka LabelCountry = Kraj +LabelCrosshair = Celownik + LabelCurrencies = Waluty LabelCurrencyConverter = Przelicznik walut diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pt.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pt.properties index 1d7d48f569..39c0140c99 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pt.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_pt.properties @@ -1185,6 +1185,8 @@ LabelCopyToClipboard = Copiar para a \u00E1rea de transfer\u00EAncia LabelCountry = Pa\u00EDs +LabelCrosshair = Mira + LabelCurrencies = Moedas LabelCurrencyConverter = Conversor de Moedas diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_ru.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_ru.properties index a6c05c2e3a..7d20923391 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_ru.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_ru.properties @@ -1185,6 +1185,8 @@ LabelCopyToClipboard = \u041A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u0 LabelCountry = \u0421\u0442\u0440\u0430\u043D\u0430 +LabelCrosshair = \u041F\u0435\u0440\u0435\u043A\u0440\u0435\u0441\u0442\u0438\u0435 + LabelCurrencies = \u0412\u0430\u043B\u044E\u0442\u044B LabelCurrencyConverter = \u041A\u043E\u043D\u0432\u0435\u0440\u0442\u0435\u0440 \u0432\u0430\u043B\u044E\u0442 diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_sk.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_sk.properties index 2bc62fcfe9..1f23349dd7 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_sk.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_sk.properties @@ -1185,6 +1185,8 @@ LabelCopyToClipboard = Kop\u00EDrovanie do schr\u00E1nky LabelCountry = Krajina +LabelCrosshair = Kri\u017Eovatka + LabelCurrencies = Meny LabelCurrencyConverter = Konverzn\u00E1 kalkula\u010Dka diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_zh.properties b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_zh.properties index 5b658f0091..10d6423c77 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_zh.properties +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages_zh.properties @@ -1185,6 +1185,8 @@ LabelCopyToClipboard = \u590D\u5236\u81F3\u526A\u8D34\u677F LabelCountry = \u56FD\u5BB6\u6216\u5730\u533A +LabelCrosshair = \u5341\u5B57\u51C6\u7EBF + LabelCurrencies = \u8D27\u5E01 LabelCurrencyConverter = \u6C47\u7387\u8F6C\u6362\u5668 diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/theme/ChartCSSHandler.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/theme/ChartCSSHandler.java index e0de38c077..68630c25c1 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/theme/ChartCSSHandler.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/theme/ChartCSSHandler.java @@ -82,7 +82,7 @@ else if (MEASUREMENT_COLOR.equalsIgnoreCase(property) && chart instanceof TimelineChart timelineChart) { Color newColor = (Color) engine.convert(value, Color.class, control.getDisplay()); - timelineChart.getMeasurementTool().setColor(newColor); + timelineChart.getChartToolsManager().setColor(newColor); } } @@ -116,7 +116,7 @@ else if (BACKGROUND_COLOR.equalsIgnoreCase(property)) else if (MEASUREMENT_COLOR.equalsIgnoreCase(property) && chart instanceof TimelineChart timelineChart) { ICSSValueConverter cssValueConverter = engine.getCSSValueConverter(String.class); - return cssValueConverter.convert(timelineChart.getMeasurementTool().getColor(), engine, null); + return cssValueConverter.convert(timelineChart.getChartToolsManager().getColor(), engine, null); } return null; diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/ChartContextMenu.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/ChartContextMenu.java index 144c8fd0d1..c8f2a24224 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/ChartContextMenu.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/ChartContextMenu.java @@ -48,7 +48,7 @@ private void configMenuAboutToShow(IMenuManager manager) { if (chart instanceof TimelineChart timelineChart) { - timelineChart.getMeasurementTool().addContextMenu(manager); + timelineChart.getChartToolsManager().addContextMenu(manager); manager.add(new Separator()); } diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/ChartToolsManager.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/ChartToolsManager.java new file mode 100644 index 0000000000..5294bf676f --- /dev/null +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/ChartToolsManager.java @@ -0,0 +1,204 @@ +package name.abuchen.portfolio.ui.util.chart; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Event; + +import name.abuchen.portfolio.ui.Images; +import name.abuchen.portfolio.ui.Messages; +import name.abuchen.portfolio.ui.util.Colors; +import name.abuchen.portfolio.ui.util.SimpleAction; +import name.abuchen.portfolio.util.Pair; + +public class ChartToolsManager +{ + interface ChartTool + { + void onMouseDown(Event e); + + void onMouseMove(Event e); + + void onMouseUp(Event e); + + void paintControl(PaintEvent e); + } + + public enum ChartTools + { + NONE("", (ch, co) -> null, null, null), // //$NON-NLS-1$ + CROSSHAIR(Messages.LabelCrosshair, CrosshairTool::new, Images.CROSSHAIR_ON, Images.CROSSHAIR_OFF), // + MEASUREMENT(Messages.LabelMeasureDistance, MeasurementTool::new, Images.MEASUREMENT_ON, Images.MEASUREMENT_OFF); // + + private String actionText; + private BiFunction createToolFactory; + private Images imageON; + private Images imageOFF; + + private ChartTools(String actionText, + BiFunction createToolFactory, + Images imageON, + Images imageOFF) + { + this.actionText = actionText; + this.createToolFactory = createToolFactory; + this.imageON = imageON; + this.imageOFF = imageOFF; + } + + String getActionText() + { + return actionText; + } + + Images getImageOFF() + { + return imageOFF; + } + + Images getImageON() + { + return imageON; + } + + ChartTool createTool(TimelineChart chart, Color color) + { + return createToolFactory.apply(chart, color); + } + } + + record Spot(int time, LocalDate date, double xCoordinate, double value) + { + public static Spot from(Event e, TimelineChart chart) + { + double xCoordinate = chart.getAxisSet().getXAxis(0).getDataCoordinate(e.x); + LocalDate date = Instant.ofEpochMilli((long) xCoordinate).atZone(ZoneId.systemDefault()).toLocalDate(); + double value = chart.getAxisSet().getYAxis(0).getDataCoordinate(e.y); + + return new Spot(e.time, date, xCoordinate, value); + } + + public Point toPoint(TimelineChart chart) + { + return new Point(chart.getAxisSet().getXAxis(0).getPixelCoordinate(xCoordinate), + chart.getAxisSet().getYAxis(0).getPixelCoordinate(value)); + } + } + + public static final int PADDING = 5; + private ChartTools activeTool = ChartTools.NONE; + private ChartTool currentTool = null; + private Color color = Colors.BLACK; + private TimelineChart chart; + private ArrayList> buttons = new ArrayList<>(); + + public ChartToolsManager(TimelineChart chart) + { + this.chart = chart; + chart.getPlotArea().addListener(SWT.MouseDown, this::onMouseDown); + chart.getPlotArea().addListener(SWT.MouseMove, this::onMouseMove); + chart.getPlotArea().addListener(SWT.MouseUp, this::onMouseUp); + + chart.getPlotArea().addPaintListener(this::paintControl); + } + + private void onMouseDown(Event e) + { + if (currentTool == null) + return; + + currentTool.onMouseDown(e); + } + + private void onMouseMove(Event e) + { + if (currentTool == null) + return; + + currentTool.onMouseMove(e); + } + + private void onMouseUp(Event e) + { + if (currentTool == null) + return; + + currentTool.onMouseUp(e); + } + + private void paintControl(PaintEvent e) + { + if (currentTool == null) + return; + + currentTool.paintControl(e); + } + + public Color getColor() + { + return color; + } + + public void setColor(Color color) + { + this.color = color; + } + + public void addButtons(ToolBarManager toolBar) + { + createActions().forEach(p -> { + toolBar.add(p.getRight()); + buttons.add(p); + }); + } + + private List> createActions() + { + return List.of(createAction(ChartTools.CROSSHAIR), createAction(ChartTools.MEASUREMENT)); + } + + private Pair createAction(ChartTools tool) + { + var action = new SimpleAction(tool.getActionText(), // + this.activeTool == tool ? tool.getImageON() : tool.getImageOFF(), // + a -> { + if (this.activeTool == tool) + this.activeTool = ChartTools.NONE; + else + this.activeTool = tool; + + currentTool = this.activeTool.createTool(chart, color); + + // update images of tool bar buttons + updateButtonImages(this.activeTool); + + chart.getToolTip().setActive(this.activeTool == ChartTools.NONE); + chart.redraw(); + }); + + return new Pair<>(tool, action); + } + + private void updateButtonImages(ChartTools activeTool) + { + buttons.forEach(p -> p.getRight().setImageDescriptor( + (p.getLeft() == activeTool ? p.getLeft().getImageON() : p.getLeft().getImageOFF()) + .descriptor())); + } + + public void addContextMenu(IMenuManager manager) + { + createActions().forEach(p -> manager.add(p.getRight())); + } +} diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/CrosshairTool.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/CrosshairTool.java new file mode 100644 index 0000000000..dae43a2508 --- /dev/null +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/CrosshairTool.java @@ -0,0 +1,198 @@ +package name.abuchen.portfolio.ui.util.chart; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Event; +import org.swtchart.IAxis; + +import name.abuchen.portfolio.money.Values; +import name.abuchen.portfolio.ui.util.Colors; +import name.abuchen.portfolio.ui.util.chart.ChartToolsManager.ChartTool; + +class CrosshairTool implements ChartTool +{ + private final TimelineChart chart; + private Point p1; + private Color color; + + CrosshairTool(TimelineChart chart, Color color) + { + this.chart = chart; + this.color = color; + } + + @Override + public void onMouseDown(Event e) + { + if (e.button != 1) + return; + p1 = new Point(e.x, e.y); + + chart.redraw(); + } + + @Override + public void onMouseMove(Event e) + { + // no need to react on mouse moving + } + + @Override + public void onMouseUp(Event e) + { + // no need to react on mouse up + } + + @Override + public void paintControl(PaintEvent e) + { + if (p1 == null) + return; + + drawCrosshair(e, p1); + } + + private LocalDate getDateTime(Point p) + { + return Instant.ofEpochMilli((long) chart.getAxisSet().getXAxis(0).getDataCoordinate(p.x)) + .atZone(ZoneId.systemDefault()).toLocalDate(); + } + + private void drawCrosshairDateTextbox(PaintEvent e, Point p) + { + LocalDate date = getDateTime(p); + String xText = Values.Date.format(date); + + // Add margin to text + Point txtXExtend = e.gc.textExtent(xText); + txtXExtend.x += 5; + txtXExtend.y += 5; + + Point rectPoint = new Point(0, e.height - txtXExtend.y - 2); + Point textPoint = new Point(0, e.height - txtXExtend.y + 1); + + // Visual shift vertical of the container + if (p.x <= e.width / 2) + { + rectPoint.x = p.x + 5; + textPoint.x = p.x + 8; + } + else + { + rectPoint.x = p.x - txtXExtend.x - 5; + textPoint.x = p.x - txtXExtend.x - 1; + } + + e.gc.setBackground(Colors.brighter(color)); + e.gc.setForeground(Colors.getTextColor(color)); + + e.gc.fillRoundRectangle(rectPoint.x, rectPoint.y, txtXExtend.x, txtXExtend.y, ChartToolsManager.PADDING, + ChartToolsManager.PADDING); + e.gc.drawText(xText, textPoint.x, textPoint.y, true); + } + + private void drawCrosshairValueTextbox(PaintEvent e, Point p) + { + var axis = chart.getAxisSet().getYAxis(0); + String yText = chart.getToolTip().getDefaultValueFormat().format(axis.getDataCoordinate(p.y)); + + // Add margin to text + Point txtYExtend = e.gc.textExtent(yText); + txtYExtend.x += 5; + txtYExtend.y += 5; + + Point rectPoint = new Point(e.width - txtYExtend.x - 2, 0); + Point textPoint = new Point(e.width - txtYExtend.x + 1, 0); + + // Visual shift horizontally of the container + if (p.y <= e.height / 2) + { + rectPoint.y = p.y + 4; + textPoint.y = p.y + 7; + } + else + { + rectPoint.y = p.y - txtYExtend.y - 4; + textPoint.y = p.y - txtYExtend.y - 1; + } + + e.gc.setBackground(Colors.brighter(color)); + e.gc.setForeground(Colors.getTextColor(color)); + + e.gc.fillRoundRectangle(rectPoint.x, rectPoint.y, txtYExtend.x, txtYExtend.y, ChartToolsManager.PADDING, + ChartToolsManager.PADDING); + e.gc.drawText(yText, textPoint.x, textPoint.y, true); + } + + private void drawCrosshairValueSecondAxisTextbox(PaintEvent e, Point p) + { + IAxis axis = chart.getAxisSet().getYAxis(2); + if (!axis.getTick().isVisible()) + return; + + var axisFormat = axis.getTick().getFormat(); + String yText = ""; //$NON-NLS-1$ + if (axisFormat != null) + { + yText = axisFormat.format(axis.getDataCoordinate(p.y)); + } + + // Add margin to text + Point txtYExtend = e.gc.textExtent(yText); + txtYExtend.x += 5; + txtYExtend.y += 5; + + Point rectPoint = new Point(1, 0); + Point textPoint = new Point(3, 0); + + // Visual shift horizontally of the container + if (p.y <= e.height / 2) + { + rectPoint.y = p.y + 4; + textPoint.y = p.y + 7; + } + else + { + rectPoint.y = p.y - txtYExtend.y - 4; + textPoint.y = p.y - txtYExtend.y - 1; + } + + e.gc.setBackground(Colors.brighter(color)); + e.gc.setForeground(Colors.getTextColor(color)); + + e.gc.fillRoundRectangle(rectPoint.x, rectPoint.y, txtYExtend.x, txtYExtend.y, ChartToolsManager.PADDING, + ChartToolsManager.PADDING); + e.gc.drawText(yText, textPoint.x, textPoint.y, true); + } + + private void drawCrosshair(PaintEvent e, Point p) + { + e.gc.setLineWidth(1); + e.gc.setLineStyle(SWT.LINE_SOLID); + e.gc.setForeground(color); + e.gc.setBackground(color); + e.gc.setAntialias(SWT.ON); + + // draw crosshair + e.gc.drawLine(p.x, 0, p.x, e.height); + e.gc.drawLine(0, p.y, e.width, p.y); + + // set textbox style + e.gc.setForeground(Colors.theme().defaultForeground()); + e.gc.setBackground(Colors.theme().defaultBackground()); + e.gc.setAlpha(220); + + // value for horizontal axis + drawCrosshairDateTextbox(e, p); + + drawCrosshairValueTextbox(e, p); + drawCrosshairValueSecondAxisTextbox(e, p); + } + +} diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/MeasurementTool.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/MeasurementTool.java index e202bbd471..ef8b3b884a 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/MeasurementTool.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/MeasurementTool.java @@ -1,16 +1,8 @@ package name.abuchen.portfolio.ui.util.chart; import java.text.DecimalFormat; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.action.IMenuManager; -import org.eclipse.jface.action.ToolBarManager; -import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.SWT; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.graphics.Color; @@ -19,117 +11,43 @@ import org.eclipse.swt.widgets.Event; import name.abuchen.portfolio.money.Values; -import name.abuchen.portfolio.ui.Images; -import name.abuchen.portfolio.ui.Messages; import name.abuchen.portfolio.ui.util.Colors; -import name.abuchen.portfolio.ui.util.SimpleAction; +import name.abuchen.portfolio.ui.util.chart.ChartToolsManager.ChartTool; +import name.abuchen.portfolio.ui.util.chart.ChartToolsManager.Spot; -public class MeasurementTool -{ - private static record Spot(int time, LocalDate date, double xCoordinate, double value) - { - public static Spot from(Event e, TimelineChart chart) - { - double xCoordinate = chart.getAxisSet().getXAxis(0).getDataCoordinate(e.x); - LocalDate date = Instant.ofEpochMilli((long) xCoordinate).atZone(ZoneId.systemDefault()).toLocalDate(); - double value = chart.getAxisSet().getYAxis(0).getDataCoordinate(e.y); - return new Spot(e.time, date, xCoordinate, value); - } - - public Point toPoint(TimelineChart chart) - { - return new Point(chart.getAxisSet().getXAxis(0).getPixelCoordinate(xCoordinate), - chart.getAxisSet().getYAxis(0).getPixelCoordinate(value)); - } - } +class MeasurementTool implements ChartTool +{ public static final int OFFSET = 10; - public static final int PADDING = 5; + public static final int PADDING = ChartToolsManager.PADDING; private final TimelineChart chart; - private ArrayList buttons = new ArrayList<>(); - private boolean isActive = false; private boolean showRelativeChange = true; - private Color color = Colors.BLACK; - private boolean redrawOnMove = false; private Spot start; private Spot end; - /* package */ MeasurementTool(TimelineChart chart) - { - this.chart = chart; - - chart.getPlotArea().addListener(SWT.MouseDown, this::onMouseDown); - chart.getPlotArea().addListener(SWT.MouseMove, this::onMouseMove); - chart.getPlotArea().addListener(SWT.MouseUp, this::onMouseUp); - - chart.getPlotArea().addPaintListener(this::paintControl); - } - - public Color getColor() - { - return color; - } + private Color color; - public void setColor(Color color) + MeasurementTool(TimelineChart chart, Color color) { + this.chart = chart; this.color = color; - } - - public void addButtons(ToolBarManager toolBar) - { - var action = createAction(); - // store buttons to update their image on context menu action - buttons.add(action); - toolBar.add(action); - - } - public void addContextMenu(IMenuManager manager) - { - manager.add(createAction()); - } - - private SimpleAction createAction() - { - return new SimpleAction(Messages.LabelMeasureDistance, // - isActive ? Images.MEASUREMENT_ON : Images.MEASUREMENT_OFF, // - a -> { - isActive = !isActive; - - if (isActive) - { - // do not show relative change for chart show - // percentages as it does not make sense once - // negative values are included - showRelativeChange = !(chart.getToolTip() - .getDefaultValueFormat() instanceof DecimalFormat fmt) - || fmt.toPattern().indexOf('%') < 0; - } - - // update images of tool bar buttons - ImageDescriptor image = isActive ? Images.MEASUREMENT_ON.descriptor() - : Images.MEASUREMENT_OFF.descriptor(); - buttons.forEach(button -> button.setImageDescriptor(image)); - - chart.getToolTip().setActive(!isActive); - - if (!isActive) - { - start = end = null; - redrawOnMove = false; - chart.redraw(); - } - }); + // do not show relative change for chart show + // percentages as it does not make sense once + // negative values are included + this.showRelativeChange = !(chart.getToolTip().getDefaultValueFormat() instanceof DecimalFormat fmt) + || fmt.toPattern().indexOf('%') < 0; } - private void onMouseDown(Event e) + @Override + public void onMouseDown(Event e) { - if (!isActive || e.button != 1) + if (e.button != 1) return; if (redrawOnMove) // click'n'click mode @@ -141,30 +59,33 @@ private void onMouseDown(Event e) chart.redraw(); } - private void onMouseMove(Event e) + @Override + public void onMouseMove(Event e) { - if (!isActive || !redrawOnMove) + if (!redrawOnMove) return; end = Spot.from(e, chart); chart.redraw(); } - private void onMouseUp(Event e) + @Override + public void onMouseUp(Event e) { - if (!isActive || start == null || e.button != 1) + if (start == null || e.button != 1) return; // if enough time has elapsed, assume it was click'n'drag // mode (otherwise continue in click'n'click mode) - if (e.time - start.time > 300) + if (e.time - start.time() > 300) redrawOnMove = false; end = Spot.from(e, chart); chart.redraw(); } - private void paintControl(PaintEvent e) + @Override + public void paintControl(PaintEvent e) { if (start == null || end == null) return; @@ -177,8 +98,8 @@ private void paintControl(PaintEvent e) { e.gc.setLineWidth(1); e.gc.setLineStyle(SWT.LINE_SOLID); - e.gc.setForeground(this.color); - e.gc.setBackground(this.color); + e.gc.setForeground(color); + e.gc.setBackground(color); e.gc.setAntialias(SWT.ON); e.gc.drawLine(p1.x, p1.y, p2.x, p2.y); @@ -199,8 +120,8 @@ private void paintControl(PaintEvent e) Point txtExtend = e.gc.textExtent(text); Rectangle plotArea = chart.getPlotArea().getClientArea(); - e.gc.setBackground(Colors.brighter(this.color)); - e.gc.setForeground(Colors.getTextColor(this.color)); + e.gc.setBackground(Colors.brighter(color)); + e.gc.setForeground(Colors.getTextColor(color)); if (p2.x < plotArea.width / 2) { @@ -218,7 +139,8 @@ private void paintControl(PaintEvent e) p2.y - txtExtend.y / 2 - PADDING, // txtExtend.x + 2 * PADDING, // txtExtend.y + 2 * PADDING, PADDING, PADDING); - e.gc.drawText(text, p2.x - OFFSET - PADDING - txtExtend.x, p2.y - txtExtend.y / 2, true); + e.gc.drawText(text, p2.x - OFFSET - PADDING - txtExtend.x, p2.y - txtExtend.y / 2, + true); } } finally diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/TimelineChart.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/TimelineChart.java index 33f0be1214..9190e2d98a 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/TimelineChart.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/util/chart/TimelineChart.java @@ -80,7 +80,7 @@ public long getTimeMillis() private List nonTradingDayMarkers = new ArrayList<>(); private Map addedAxis = new HashMap<>(); - private MeasurementTool measurementTool; + private ChartToolsManager chartTools; private TimelineChartToolTip toolTip; private ChartContextMenu contextMenu; @@ -140,7 +140,7 @@ public boolean drawBehindSeries() toolTip = new TimelineChartToolTip(this); - measurementTool = new MeasurementTool(this); + chartTools = new ChartToolsManager(this); ZoomMouseWheelListener.attachTo(this); MovePlotKeyListener.attachTo(this); @@ -242,9 +242,9 @@ public TimelineChartToolTip getToolTip() return toolTip; } - public MeasurementTool getMeasurementTool() + public ChartToolsManager getChartToolsManager() { - return measurementTool; + return chartTools; } private void paintTimeGrid(PaintEvent e) diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/SecuritiesChart.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/SecuritiesChart.java index 3813e23558..a0c2f4026d 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/SecuritiesChart.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/SecuritiesChart.java @@ -614,7 +614,7 @@ private final void readChartConfig(Client client) public void addButtons(ToolBarManager toolBar) { - chart.getMeasurementTool().addButtons(toolBar); + chart.getChartToolsManager().addButtons(toolBar); toolBar.add(new Separator()); List viewActions = new ArrayList<>();