001package jmri.jmrit.etcs.dmi.swing;
002
003import java.awt.Color;
004import java.awt.Font;
005import java.awt.event.ActionEvent;
006import java.beans.PropertyChangeListener;
007import java.time.format.DateTimeFormatter;
008
009import javax.annotation.Nonnull;
010import javax.swing.*;
011
012import jmri.InstanceManager;
013import jmri.Timebase;
014import jmri.jmrit.etcs.ResourceUtil;
015import jmri.util.TimerUtil;
016
017/**
018 * Class to demonstrate features of ERTMS DMI Panel G,
019 * Automatic Train Operation and clock.
020 * @author Steve Young Copyright (C) 2024
021 */
022public class DmiPanelG extends JPanel {
023
024    private final DmiPanel main;
025    private final JLabel timeLabel = new JLabel();
026    private final JLabel g2g3g4LabelTop;
027    private final JLabel g2g3g4LabelBottom;
028
029    private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");
030    private final Timebase clock = InstanceManager.getDefault(Timebase.class);
031
032    private final transient PropertyChangeListener clockTickListener = e -> updateClock();
033    private transient java.util.TimerTask secondTimer;
034    private final transient PropertyChangeListener clockPauseFlashListener =
035        e -> timeLabel.setVisible(!timeLabel.isVisible());
036    private final transient PropertyChangeListener clockRunStateChangedListener = e -> clockRunStateChanged();
037    private boolean disposed = false;
038
039    private final JButton g1Button;
040    private final JButton g2Button;
041    private final JButton g3Button;
042    private final JLabel g4Label;
043    private final JLabel g3LabelMins;
044    private final JLabel g3LabelSecs;
045    private final JButton g5Button;
046
047    public DmiPanelG(@Nonnull DmiPanel mainPanel){
048        super();
049        setLayout(null);
050
051        setBackground(DmiPanel.BACKGROUND_COLOUR);
052        setBounds(334, 315, 246, 150);
053
054        main = mainPanel;
055        JToggleButton g12PositionButton = new JToggleButton();
056
057        g12PositionButton.setBounds(63,100,120,50);
058        g12PositionButton.setBorder(javax.swing.BorderFactory.createLineBorder(Color.black, 1));
059        g12PositionButton.setBackground(DmiPanel.BACKGROUND_COLOUR);
060
061        g12PositionButton.setIcon(ResourceUtil.getImageIcon("DR_03.bmp"));
062        add(g12PositionButton);
063
064        g12PositionButton.setFocusable(false);
065
066        // position G13
067        timeLabel.setBounds(183, 100, 63, 50);
068        timeLabel.setForeground(DmiPanel.GREY);
069        timeLabel.setBackground(DmiPanel.BACKGROUND_COLOUR);
070        timeLabel.setBorder(javax.swing.BorderFactory.createLineBorder(Color.black, 1));
071        timeLabel.setFont(new Font(DmiPanel.FONT_NAME, Font.PLAIN, 13));
072        timeLabel.setHorizontalAlignment(SwingConstants.CENTER);
073        add(timeLabel);
074
075        clock.addMinuteChangeListener(clockTickListener);
076        clock.addPropertyChangeListener(Timebase.PROPERTY_CHANGE_RATE, clockTickListener);
077        clock.addPropertyChangeListener(Timebase.PROPERTY_CHANGE_RUN, clockRunStateChangedListener);
078        jmri.util.ThreadingUtil.runOnGUIEventually(() -> {
079            clockRunStateChanged();
080            updateClock();
081        });
082
083        g2g3g4LabelTop = new JLabel();
084        g2g3g4LabelTop.setBounds(49,4,147,25);
085        g2g3g4LabelTop.setForeground(DmiPanel.GREY);
086        g2g3g4LabelTop.setBackground(DmiPanel.BACKGROUND_COLOUR);
087        g2g3g4LabelTop.setFont(new Font(DmiPanel.FONT_NAME, Font.PLAIN, 13));
088        g2g3g4LabelTop.setHorizontalAlignment(SwingConstants.CENTER);
089        add(g2g3g4LabelTop);
090
091        g2g3g4LabelBottom = new JLabel();
092        g2g3g4LabelBottom.setBounds(49,21,147,24);
093        g2g3g4LabelBottom.setForeground(DmiPanel.GREY);
094        g2g3g4LabelBottom.setBackground(DmiPanel.BACKGROUND_COLOUR);
095        g2g3g4LabelBottom.setFont(new Font(DmiPanel.FONT_NAME, Font.PLAIN, 13));
096        g2g3g4LabelBottom.setHorizontalAlignment(SwingConstants.CENTER);
097        add(g2g3g4LabelBottom);
098
099        g1Button = new JButton();
100        g1Button.setBorder(DmiPanel.BORDER_NORMAL);
101        g1Button.setFocusable(false);
102        g1Button.setVisible(false);
103        g1Button.setBackground(DmiPanel.BACKGROUND_COLOUR);
104        g1Button.setContentAreaFilled(false); // Make the button transparent
105        g1Button.addActionListener(this::gButtonPressed);
106        g1Button.setName("g1Button");
107
108        g2Button = new JButton(); // stopping accuracy
109        g2Button.setBorder(DmiPanel.BORDER_NORMAL);
110        g2Button.setFocusable(false);
111        g2Button.setVisible(false);
112        g2Button.setBackground(DmiPanel.BACKGROUND_COLOUR);
113        g2Button.setContentAreaFilled(false); // Make the button transparent
114        g2Button.addActionListener(this::gButtonPressed);
115        g2Button.setName("g2Button");
116
117        g3Button = new JButton();
118        g3Button.setBorder(DmiPanel.BORDER_NORMAL);
119        g3Button.setFocusable(false);
120        g3Button.setVisible(false);
121        g3Button.setBackground(DmiPanel.BACKGROUND_COLOUR);
122        g3Button.setContentAreaFilled(false); // Make the button transparent
123        g3Button.addActionListener(this::gButtonPressed);
124        g3Button.setName("g3Button");
125
126        g3LabelMins = new JLabel();
127        g3LabelMins.setBounds(49+49,12,24,24);
128        g3LabelMins.setForeground(DmiPanel.GREY);
129        g3LabelMins.setBackground(DmiPanel.BACKGROUND_COLOUR);
130        g3LabelMins.setFont(new Font(DmiPanel.FONT_NAME, Font.PLAIN, 17));
131        g3LabelMins.setHorizontalAlignment(SwingConstants.RIGHT);
132        add(g3LabelMins);
133
134        g3LabelSecs = new JLabel();
135        g3LabelSecs.setBounds(49+49+24,14,24,24);
136        g3LabelSecs.setForeground(DmiPanel.GREY);
137        g3LabelSecs.setBackground(DmiPanel.BACKGROUND_COLOUR);
138        g3LabelSecs.setFont(new Font(DmiPanel.FONT_NAME, Font.PLAIN, 13));
139        g3LabelSecs.setHorizontalAlignment(SwingConstants.LEFT);
140        add(g3LabelSecs);
141
142        g4Label = new JLabel();
143        g4Label.setVisible(false);
144        g4Label.setBackground(DmiPanel.BACKGROUND_COLOUR);
145
146        g5Button = new JButton();
147        g5Button.setBorder(DmiPanel.BORDER_NORMAL);
148        g5Button.setFocusable(false);
149        g5Button.setVisible(false);
150        g5Button.setBackground(DmiPanel.BACKGROUND_COLOUR);
151        g5Button.setContentAreaFilled(false); // Make the button transparent
152        g5Button.addActionListener(this::gButtonPressed);
153        g5Button.setName("g5Button");
154
155        g1Button.setBounds(0,0,49,50);
156        g2Button.setBounds(49,0,49,50);
157        g3Button.setBounds(49+49,0,49,50);
158        g4Label.setBounds(49+49+49,0,49,50);
159        g5Button.setBounds(49+49+49+49,0,49,50);
160
161        add(g1Button);
162        add(g2Button);
163        add(g3Button);
164        add(g4Label);
165        add(g5Button);
166    }
167
168    /**
169     * Set Automatic Train Operation Mode.
170     * @param mode the new ATO Mode.
171     * 0: No ATO 
172     * 1: ATO selected
173     * 2: ATO Ready for Engagement
174     * 3: ATO Engaged
175     * 4: ATO Disengaging
176     * 5: ATO failure
177     */
178    protected void setAtoMode(int mode){
179        switch(mode){
180            case 1:
181                g1Button.setIcon(ResourceUtil.getImageIcon("ATO_01.bmp"));
182                g1Button.setDisabledIcon(ResourceUtil.getImageIcon("ATO_01.bmp"));
183                break;
184            case 2:
185                g1Button.setIcon(ResourceUtil.getImageIcon("ATO_02.bmp"));
186                g1Button.setDisabledIcon(ResourceUtil.getImageIcon("ATO_02.bmp"));
187                g1Button.setActionCommand(DmiPanel.PROP_CHANGE_ATO_DRIVER_REQUEST_START);
188                break;
189            case 3:
190                g1Button.setIcon(ResourceUtil.getImageIcon("ATO_03.bmp"));
191                g1Button.setDisabledIcon(ResourceUtil.getImageIcon("ATO_03.bmp"));
192                g1Button.setActionCommand(DmiPanel.PROP_CHANGE_ATO_DRIVER_REQUEST_STOP);
193                break;
194            case 4:
195                g1Button.setIcon(ResourceUtil.getImageIcon("ATO_04.bmp"));
196                g1Button.setDisabledIcon(ResourceUtil.getImageIcon("ATO_04.bmp"));
197                g1Button.setActionCommand(DmiPanel.PROP_CHANGE_ATO_DRIVER_REQUEST_STOP);
198                break;
199            case 5:
200                g1Button.setIcon(ResourceUtil.getImageIcon("ATO_05.bmp"));
201                g1Button.setDisabledIcon(ResourceUtil.getImageIcon("ATO_05.bmp"));
202                break;
203            case 0:
204            default:
205                g1Button.setIcon(null);
206                g1Button.setDisabledIcon(null);
207        }
208        g1Button.setEnabled( mode > 1 && mode < 5);
209        g1Button.setVisible(mode != 0);
210    }
211
212    /**
213     * Set Stopping accuracy symbol visible.
214     * Only valid in ATO Mode.
215     * @param acc -2: Hidden, -1: Undershot 0: Accurate 1: Overshot
216     */
217    protected void setStoppingAccuracy(int acc){
218        switch (acc){
219            case -1:
220                g2Button.setIcon(ResourceUtil.getImageIcon("ATO_07.bmp"));
221                break;
222            case  0:
223                g2Button.setIcon(ResourceUtil.getImageIcon("ATO_08.bmp"));
224                break;
225            case  1:
226                g2Button.setIcon(ResourceUtil.getImageIcon("ATO_06.bmp"));
227                 break;
228            case -2:
229            default:
230                
231        }
232        g2Button.setVisible(acc != -2);
233    }
234
235    protected void setStoppingPointLabel(String station, String eta){
236        g2g3g4LabelTop.setText(station);
237        g2g3g4LabelBottom.setText(eta);
238        g2g3g4LabelTop.setVisible(!station.isBlank());
239        g2g3g4LabelBottom.setVisible(!eta.isBlank());
240    }
241
242    protected void setDwellTime(int mins, int secs){
243        g3LabelMins.setVisible(mins > 0);
244        g3LabelSecs.setVisible(secs > -1);
245        g3LabelMins.setText(String.valueOf(mins));
246        g3LabelSecs.setText(( mins > 0 ? ":" : "")  + ( secs < 10 ? "0": "")+ secs);
247    }
248
249    protected void setDoorIcon(int mode){
250        switch (mode){
251            case 10:
252            case 11:
253            case 12:
254            case 13:
255            case 14:
256            case 15:
257            case 16:
258                g4Label.setIcon(ResourceUtil.getImageIcon("ATO_"+mode+".bmp"));
259                break;
260            case 0:
261            default:
262                g4Label.setIcon(null);
263        }
264        g4Label.setVisible(mode != 0);
265    }
266
267    protected void setSkipStoppingPoint(int mode){
268        g5Button.setEnabled(false);
269        switch (mode){
270            case 17:
271                g5Button.setActionCommand(DmiPanel.PROP_CHANGE_SKIP_STOPPING_POINT_INACTIVE_DRIVER);
272                g5Button.setIcon(ResourceUtil.getImageIcon("ATO_17.bmp"));
273                g5Button.setDisabledIcon(ResourceUtil.getImageIcon("ATO_"+mode+".bmp"));
274                g5Button.setEnabled(true);
275                break;
276            case 18:
277                g5Button.setIcon(ResourceUtil.getImageIcon("ATO_18.bmp"));
278                g5Button.setDisabledIcon(ResourceUtil.getImageIcon("ATO_"+mode+".bmp"));
279                break;
280            case 19:
281                g5Button.setActionCommand(DmiPanel.PROP_CHANGE_SKIP_STOPPING_POINT_REQUEST_DRIVER);
282                g5Button.setIcon(ResourceUtil.getImageIcon("ATO_19.bmp"));
283                g5Button.setDisabledIcon(ResourceUtil.getImageIcon("ATO_"+mode+".bmp"));
284                g5Button.setEnabled(true);
285                break;
286            case 0:
287            default:
288                g5Button.setIcon(null);
289                g5Button.setDisabledIcon(null);
290        }
291        g5Button.setVisible(mode != 0);
292    }
293
294    private void gButtonPressed(ActionEvent e){
295        main.firePropertyChange(e.getActionCommand(), false, true);
296    }
297
298    private void clockRunStateChanged(){
299        timeLabel.setVisible(clock.getRun());
300        if ( clock.getRun() ) {
301            main.removeFlashListener(clockPauseFlashListener, false);
302        } else {
303            main.addFlashListener(clockPauseFlashListener, false);
304        }
305    }
306
307    private void updateClock() {
308        restartSecondTimer();
309        updateLabel();
310    }
311
312    private void updateLabel() {
313        java.time.LocalTime now = clock.getTime().toInstant()
314            .atZone(java.time.ZoneId.systemDefault()).toLocalTime();
315        timeLabel.setText(now.format(TIME_FORMAT));
316    }
317
318    private void restartSecondTimer(){
319        if ( secondTimer != null ) {
320            secondTimer.cancel();
321            secondTimer = null;
322        }
323
324        long period = (long) (1000 / clock.userGetRate());
325        secondTimer = new java.util.TimerTask(){
326            @Override
327            public void run() {
328                if ( !disposed ) {
329                    updateLabel();
330                    TimerUtil.scheduleOnGUIThread(secondTimer, period);
331                }
332            }
333        };
334        TimerUtil.scheduleOnGUIThread(secondTimer, period);
335    }
336
337    public void dispose(){
338        clock.removeMinuteChangeListener(clockTickListener);
339        clock.removePropertyChangeListener(Timebase.PROPERTY_CHANGE_RUN, clockRunStateChangedListener);
340        clock.removePropertyChangeListener(Timebase.PROPERTY_CHANGE_RATE, clockTickListener);
341        main.removeFlashListener(clockPauseFlashListener, false);
342        disposed = true;
343    }
344
345}