001package jmri.jmrit.etcs.dmi.swing;
002
003import java.awt.*;
004import java.awt.image.BufferedImage;
005
006import javax.annotation.Nonnull;
007import javax.swing.*;
008
009import jmri.jmrit.etcs.ResourceUtil;
010
011/**
012 * JPanel containing ERTMS DMI Panel A, Distance to Target Bar.
013 * @author Steve Young Copyright (C) 2024
014 */
015public class DmiPanelA extends JPanel {
016
017    private final JLabel a2Label;
018    private final JLabel a4Label;
019
020    private String speedString = "";
021    private int distanceToTarget = -10;
022
023    private static final BufferedImage supervisionImage = ResourceUtil.readFile(ResourceUtil.getImageFile("LS_01.bmp"));
024    private static final ImageIcon adhesionIcon =  ResourceUtil.getImageIcon("ST_02.bmp");
025
026    public DmiPanelA(@Nonnull DmiPanel mainPanel){
027        super();
028        setLayout(null);
029        setBackground(DmiPanel.BACKGROUND_COLOUR);
030        setBounds(0, 15, 54, 300);
031
032        JPanel a1 = getSupervisionImagePanel();
033        a2Label = new JLabel();
034        // JPanel a2 = new JPanel(); // distance to target, nearest 10m
035        JPanel a3 = getDistanceToTargetBarPanel(); // distance to target bar
036        JPanel a4 = new JPanel(); // adhesion factor
037
038        a1.setBounds(0, 0, 54, 54);
039        a2Label.setBounds(0, 54, 54, 30);
040        a3.setBounds(0, 84, 54, 191);
041        a4.setBounds(0, 84+191, 54, 25);
042
043        a1.setLayout(null);
044        a1.setOpaque(true);
045
046        a2Label.setForeground(DmiPanel.GREY);
047        a2Label.setFont(new Font(DmiPanel.FONT_NAME, Font.PLAIN, 14));
048        a2Label.setHorizontalAlignment(SwingConstants.RIGHT);
049
050        a4Label = new JLabel();
051        a4.add(a4Label);
052
053        // setBg(a2);
054        setBg(a3);
055        setBg(a4);
056        
057        add(a1);
058        add(a2Label);
059        add(a3);
060        add(a4);
061
062        DmiPanelA.this.setAdhesionFactorOn(false);
063        DmiPanelA.this.setLimitedSupervisionSpeed(-1);
064    }
065
066    private JPanel getDistanceToTargetBarPanel(){
067        return new JPanel() {
068            @Override
069            protected void paintComponent(Graphics g) {
070                if (distanceToTarget < 0 ) {
071                    return;
072                }
073                if (!(g instanceof Graphics2D) ) {
074                    throw new IllegalArgumentException("Graphics object passed is not the correct type");
075                }
076                Graphics2D g2 = (Graphics2D) g;
077
078                RenderingHints  hints =new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
079                hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
080                g2.setRenderingHints(hints);
081                
082                g2.setColor(DmiPanel.GREY);
083                drawScale(g2);
084
085                int rectHeight = (int)calculatePositionOnScale(distanceToTarget);
086                g2.fillRect(29, 188-rectHeight, 10, rectHeight);
087            }
088        };
089    }
090
091    private static final double LINEAR_SCALE_MAX_DISTANCE = 100.0;
092    private static final double LOG_SCALE_MIN_DISTANCE = 100.0;
093    private static final double LOG_SCALE_MAX_DISTANCE = 1000.0;
094    private static final int TOTAL_PIXELS = 188;
095    private static final int FIRST_100M_PIXELS = 33;
096    private static final int LOG_SCALE_WIDTH_PIXELS = TOTAL_PIXELS-FIRST_100M_PIXELS;
097
098    // Calculate the position on the scale for a given length in meters
099    private static double calculatePositionOnScale(double lengthInMeters) {
100        double position;
101        if (lengthInMeters <= LINEAR_SCALE_MAX_DISTANCE) {
102            // Linear scale for the first 100 meters
103            position = (lengthInMeters / LINEAR_SCALE_MAX_DISTANCE) * FIRST_100M_PIXELS;
104        } else {
105            // Logarithmic scale for lengths beyond 100 meters
106            double logScaleFactor = LOG_SCALE_WIDTH_PIXELS / (Math.log(LOG_SCALE_MAX_DISTANCE)
107                - Math.log(LOG_SCALE_MIN_DISTANCE));
108            position = FIRST_100M_PIXELS + (Math.log(lengthInMeters)
109                - Math.log(LOG_SCALE_MIN_DISTANCE)) * logScaleFactor;
110        }
111        log.debug("at distance {} px: {}",lengthInMeters,position);
112        return position;
113    }
114
115    private void drawScale(Graphics2D g2){
116        g2.drawLine(12, 1, 25, 1); // 1000
117        g2.drawLine(12, 2, 25, 2); // 
118        g2.drawLine(16, 8, 25, 8); // 900
119        g2.drawLine(16, 15, 25, 15); // 800
120        g2.drawLine(16, 24, 25, 24); // 700
121        g2.drawLine(16, 34, 25, 34); // 600
122        g2.drawLine(12, 47, 25, 47); // 500
123        g2.drawLine(12, 48, 25, 48); // 
124        g2.drawLine(16, 61, 25, 61); // 400
125        g2.drawLine(16, 81, 25, 81); // 300
126        g2.drawLine(16, 107, 25, 107); // 200
127        g2.drawLine(16, 154, 25, 154); // 100
128        g2.drawLine(12, 187, 25, 187); // 0
129        g2.drawLine(12, 188, 25, 188); // 
130    }
131
132    private JPanel getSupervisionImagePanel(){
133        return new JPanel() {
134            @Override
135            protected void paintComponent(Graphics g) {
136                if (speedString.isEmpty()) {
137                    return;
138                }
139                if (!(g instanceof Graphics2D) ) {
140                    throw new IllegalArgumentException("Graphics object passed is not the correct type");
141                }
142                Graphics2D g2 = (Graphics2D) g;
143
144                RenderingHints  hints =new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
145                hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
146                g2.setRenderingHints(hints);
147
148                Font font = new Font(DmiPanel.FONT_NAME, Font.PLAIN, 18);
149                g2.setFont(font);
150                g2.setColor(DmiPanel.GREY);
151                
152                FontMetrics fm = g2.getFontMetrics();
153                int textWidth = fm.stringWidth(speedString);
154                int centerX =  ((getWidth()-textWidth) / 2);
155
156                g2.drawImage(supervisionImage, 2, 2, 50, 
157                        50, this);
158                g2.drawString(speedString, centerX, 33);
159            }
160        };
161    }
162
163    protected void setLimitedSupervisionSpeed(int spd){
164        if ( spd < 0 ) {
165            speedString="";
166        } else {
167            speedString = (String.valueOf(spd));
168        }
169        repaint();
170    }
171
172    protected void setAdhesionFactorOn(boolean newVal) {
173        a4Label.setIcon(newVal ? adhesionIcon : null);
174        a4Label.setToolTipText(newVal ?  Bundle.getMessage("AdhesionFactorOn") : null);
175    }
176
177    protected void setDistanceToTarget(int distance) {
178        distanceToTarget = distance;
179        a2Label.setVisible(distanceToTarget >= 0 );
180        int nearestTen = ((distanceToTarget + 5) / 10) * 10;
181        a2Label.setText(String.valueOf(nearestTen));
182        repaint();
183    }
184
185    private void setBg(JPanel p){
186        p.setBackground(DmiPanel.BACKGROUND_COLOUR);
187        p.setBorder(javax.swing.BorderFactory.createLineBorder(Color.black, 1));
188    
189    }
190
191    protected void advance(int distance) {
192        setDistanceToTarget(distanceToTarget - distance);
193    }
194
195    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DmiPanelA.class);
196
197}