001package jmri.jmrit.etcs.dmi.swing;
002
003import java.awt.*;
004import java.awt.event.ActionEvent;
005import java.beans.PropertyChangeEvent;
006import java.beans.PropertyChangeListener;
007
008import javax.annotation.Nonnull;
009import javax.swing.*;
010
011import jmri.jmrit.etcs.ResourceUtil;
012
013/**
014 * Class to display features of ERTMS DMI Panel C,
015 * Large Buttons underneath Speedometer.
016 * @author Steve Young Copyright (C) 2024
017 */
018public class DmiPanelC extends JPanel {
019
020    private final JButton c1Button;
021    private final JButton c2Button;
022    private final JLabel c3Label;
023    private final JPanel c1Panel;
024    private final JLabel c6Label;
025    private final JLabel c8Label;
026    private final JLabel c9Label;
027    private final DmiPanel mainPanel;
028
029    private final transient PropertyChangeListener pclC1 = (PropertyChangeEvent evt) ->  changeC1Border(); 
030    private final transient PropertyChangeListener pclC2 = (PropertyChangeEvent evt) ->  changeC2Border(); 
031    private boolean nextc1FlashState = true;
032    private boolean nextc2FlashState = true;
033
034    public DmiPanelC(@Nonnull DmiPanel panel){
035        super();
036        setLayout(null);
037
038        setBackground(DmiPanel.BACKGROUND_COLOUR);
039        setBounds(0, 315, 334, 50);
040
041        mainPanel = panel;
042
043        c1Panel = new JPanel();
044        c1Panel.setLayout(new GridLayout(1, 1)); // Using GridLayout with 1 row and 1 column
045
046        JPanel c2Panel = new JPanel(); // tunnel stopping area
047        JPanel c3Panel = new JPanel();
048        JPanel c4Panel = new JPanel();
049        JPanel c5Panel = new JPanel();
050        JPanel c6Panel = new JPanel();
051        JPanel c7Panel = new JPanel();
052        JPanel c8Panel = new JPanel();
053        JPanel c9Panel = new JPanel();
054
055        c8Panel.setBounds(0, 0, 54, 25);
056        c9Panel.setBounds(0, 25, 54, 25);
057        c2Panel.setBounds(54, 0, 37, 50);
058        c3Panel.setBounds(54+37, 0, 37, 50);
059        c4Panel.setBounds(54+37+37, 0, 37, 50);
060        c1Panel.setBounds(54+37+37+37, 0, 58, 50);
061        c5Panel.setBounds(54+37+37+37+58, 0, 37, 50);
062        c6Panel.setBounds(54+37+37+37+58+37, 0, 37, 50);
063        c7Panel.setBounds(54+37+37+37+58+37+37, 0, 37, 50);
064
065        c6Label = new JLabel();
066        c8Label = new JLabel();
067        c9Label = new JLabel();
068
069        c6Panel.add(c6Label);
070        c8Panel.add(c8Label);
071        c9Panel.add(c9Label);
072
073        setBg(c1Panel);
074        setBg(c2Panel);
075        setBg(c3Panel);
076        setBg(c4Panel);
077        setBg(c5Panel);
078        setBg(c6Panel);
079        setBg(c7Panel);
080        setBg(c8Panel);
081        setBg(c9Panel);
082
083        c1Panel.setBackground(DmiPanel.BACKGROUND_COLOUR);
084
085        c1Button = new JButton(); // Level Transition
086        c1Button.setBorder(DmiPanel.BORDER_NORMAL);
087        c1Button.setFocusable(false);
088        c1Button.setVisible(false);
089        c1Button.setBackground(DmiPanel.BACKGROUND_COLOUR);
090        c1Button.setContentAreaFilled(false); // Make the button transparent
091        c1Button.addActionListener(this::acknowledgeLevelPressed);
092        c1Button.setName("levelTransitionNotificationButton");
093
094        c2Button = new JButton();
095        c2Button.setBorder(DmiPanel.BORDER_NORMAL);
096        c2Button.setFocusable(false);
097        c2Button.setVisible(false);
098        c2Button.setBackground(DmiPanel.BACKGROUND_COLOUR);
099        c2Button.setContentAreaFilled(false); // Make the button transparent
100        c2Button.addActionListener(this::acknowledgeTunnelStopPressed);
101        c2Button.setName("TunnelStopNotificationButton");
102        // Tunnel Stopping Area Announce
103        c2Button.setIcon(ResourceUtil.getImageIcon("TC_37.bmp"));
104
105        // Tunnel Stopping Area
106        c2Button.setDisabledIcon(ResourceUtil.getImageIcon("TC_36.bmp"));
107
108        c3Label = new JLabel();
109        c3Label.setBounds(54+37, 0, 37+37, 50);
110        c3Label.setForeground(DmiPanel.GREY);
111        c3Label.setVerticalAlignment(SwingConstants.CENTER);
112        c3Label.setHorizontalAlignment(SwingConstants.CENTER);
113        c3Label.setFont(new Font(DmiPanel.FONT_NAME, Font.BOLD, 18));
114        add(c3Label);
115
116        c1Panel.add(c1Button);
117        c2Panel.add(c2Button);
118        // c3Panel.add(c3Label);
119
120        add(c8Panel);
121        add(c9Panel);
122        add(c2Panel);
123        add(c3Panel);
124        add(c4Panel);
125        add(c1Panel);
126        add(c5Panel);
127        add(c6Panel);
128        add(c7Panel);
129
130        DmiPanelC.this.setLevel(-2);
131        DmiPanelC.this.setTunnelStoppingDistance(0);
132        DmiPanelC.this.setLevelTransition(-2, false);
133    }
134
135    /**
136     * Set ERTMS Level Transition symbol / button active.
137     * Note that some valid options for ERTMS3.6 are invalid for ERTMS4 ,
138     * e.g. 2, false.
139     * 
140     * @param newLevel the level to set.
141     * @param ackRequired true if acknowledgement required by driver, false if automatic.
142     */
143    protected void setLevelTransition(int newLevel, boolean ackRequired) {
144        mainPanel.removeFlashListener(pclC1, false);
145        c1Button.setBorder(DmiPanel.BORDER_NORMAL);
146        setC1LevelTransitionIcon(newLevel, ackRequired);
147        if ( ackRequired ) {
148            startC1Flash();
149        }
150        
151    }
152
153    protected void setModeAcknowledge(int newMode){
154        c1Button.setVisible(newMode != DmiPanel.MODE_NONE);
155        mainPanel.removeFlashListener(pclC1, false);
156        if (newMode == DmiPanel.MODE_NONE) {
157            return;
158        }
159        startC1Flash();
160        c1Button.setEnabled(true);
161        c1Button.setIcon(getC1ModeAcknowledgeIcon(newMode));
162        c1Button.setSelectedIcon(getC1ModeAcknowledgeIcon(newMode));
163        c1Button.setActionCommand(getC1ActionEventText(newMode));
164    }
165
166    private static Icon getC1ModeAcknowledgeIcon(int newMode){
167        switch (newMode) {
168            case DmiPanel.MODE_SHUNTING:
169                return ResourceUtil.getImageIcon( "MO_02.bmp");
170            case DmiPanel.MODE_TRIP:
171                return ResourceUtil.getImageIcon( "MO_05.bmp");
172            case DmiPanel.MODE_ON_SIGHT:
173                return ResourceUtil.getImageIcon( "MO_08.bmp");
174            case DmiPanel.MODE_STAFF_RESPONSIBLE:
175                return ResourceUtil.getImageIcon( "MO_10.bmp");
176            case DmiPanel.MODE_REVERSING:
177                return ResourceUtil.getImageIcon( "MO_15.bmp");
178            case DmiPanel.MODE_UNFITTED:
179                return ResourceUtil.getImageIcon( "MO_17.bmp");
180            case DmiPanel.MODE_NATIONAL_SYSTEM:
181                return ResourceUtil.getImageIcon( "MO_20.bmp");
182            case DmiPanel.MODE_LIMITED_SUPERVISION:
183                return ResourceUtil.getImageIcon( "MO_22.bmp");
184            default:
185                log.warn("No Icon for C1 SetLevel with Confirmation {}", newMode );
186                return null;
187        }
188    }
189
190    private String getC1ActionEventText(int mode) {
191        switch (mode) {
192            case DmiPanel.MODE_SHUNTING:
193                return DmiPanel.PROP_CHANGE_MODE_SHUNTING_ACK;
194            case DmiPanel.MODE_TRIP:
195                return DmiPanel.PROP_CHANGE_MODE_TRIP_ACK;
196            case DmiPanel.MODE_ON_SIGHT:
197                return DmiPanel.PROP_CHANGE_MODE_ON_SIGHT_ACK;
198            case DmiPanel.MODE_STAFF_RESPONSIBLE:
199                return DmiPanel.PROP_CHANGE_MODE_STAFF_RESPONSIBLE_ACK;
200            case DmiPanel.MODE_REVERSING:
201                return DmiPanel.PROP_CHANGE_MODE_REVERSING_ACK;
202            case DmiPanel.MODE_UNFITTED:
203                return DmiPanel.PROP_CHANGE_MODE_UNFITTED_ACK;
204            case DmiPanel.MODE_NATIONAL_SYSTEM:
205                return DmiPanel.PROP_CHANGE_MODE_NATIONAL_SYSTEM_ACK;
206            case DmiPanel.MODE_LIMITED_SUPERVISION:
207                return DmiPanel.PROP_CHANGE_MODE_LIMITED_SUPERVISION_ACK;
208            default:
209                log.warn("No ActionText for C1 SetLevel with Confirmation {}", mode );
210                return null;
211        }
212    }
213
214    private void startC1Flash(){
215        nextc1FlashState = true;
216        changeC1Border();
217        mainPanel.addFlashListener(pclC1, false);
218    }
219
220    private void setC1LevelTransitionIcon(int newLevel, boolean ackRequired) {
221        if ( ackRequired ) {
222            switch (newLevel) {
223                case -1: // ntc
224                    c1Button.setIcon(ResourceUtil.getImageIcon( "LE_09.bmp"));
225                    c1Button.setActionCommand(DmiPanel.PROP_CHANGE_LEVEL_NTC_TRANSITION_ACK);
226                    break;
227                case 0:
228                    c1Button.setIcon(ResourceUtil.getImageIcon("LE_07.bmp"));
229                    c1Button.setActionCommand(DmiPanel.PROP_CHANGE_LEVEL_0_TRANSITION_ACK);
230                    break;
231                case 1:
232                    c1Button.setIcon(ResourceUtil.getImageIcon("LE_11.bmp"));
233                    c1Button.setActionCommand(DmiPanel.PROP_CHANGE_LEVEL_1_TRANSITION_ACK);
234                    break;
235                case 2:
236                    c1Button.setIcon(ResourceUtil.getImageIcon("LE_13.bmp"));
237                    c1Button.setActionCommand(DmiPanel.PROP_CHANGE_LEVEL_2_TRANSITION_ACK);
238                    break;
239                case 3:
240                    c1Button.setIcon(ResourceUtil.getImageIcon("LE_15.bmp"));
241                    c1Button.setActionCommand(DmiPanel.PROP_CHANGE_LEVEL_3_TRANSITION_ACK);
242                    break;
243                default:
244                    c1Button.setIcon(null);
245            }
246        } else { // ack not required
247            // icons are set for both states to ensure displayed.
248            // some icons are not in ERTMS4 so disabled Icon used for both.
249            switch (newLevel) {
250                case -1: // ntc
251                    c1Button.setIcon(ResourceUtil.getImageIcon("LE_08.bmp"));
252                    c1Button.setDisabledIcon(ResourceUtil.getImageIcon("LE_08.bmp"));
253                    break;
254                case 0:
255                    c1Button.setIcon(ResourceUtil.getImageIcon("LE_06.bmp"));
256                    c1Button.setDisabledIcon(ResourceUtil.getImageIcon("LE_06.bmp"));
257                    break;
258                case 1:
259                    c1Button.setIcon(ResourceUtil.getImageIcon("LE_10.bmp"));
260                    c1Button.setDisabledIcon(ResourceUtil.getImageIcon("LE_10.bmp"));
261                    break;
262                case 2:
263                    c1Button.setIcon(ResourceUtil.getImageIcon("LE_12.bmp"));
264                    c1Button.setDisabledIcon(ResourceUtil.getImageIcon("LE_12.bmp"));
265                    break;
266                case 3:
267                    c1Button.setIcon(ResourceUtil.getImageIcon("LE_14.bmp"));
268                    c1Button.setDisabledIcon(ResourceUtil.getImageIcon("LE_14.bmp"));
269                    break;
270                default:
271                    c1Button.setIcon(null);
272                    c1Button.setDisabledIcon(null);
273            }
274        }
275        c1Button.setEnabled(ackRequired);
276        c1Button.setVisible(newLevel > -2);
277    }
278
279    protected void setTunnelStoppingIconVisible(boolean visible, boolean ackReqd){
280        c2Button.setEnabled(ackReqd);
281        c2Button.setBorder( ackReqd ? DmiPanel.BORDER_ACK : DmiPanel.BORDER_NORMAL);
282        c2Button.setVisible(visible);
283        if ( visible && ackReqd ){
284            mainPanel.addFlashListener(pclC2, false);
285        } else {
286            mainPanel.removeFlashListener(pclC2, false);
287        }
288    }
289
290    /**
291     * No value displayed if distance < 1
292     * @param distance in m to stopping area.
293     * 
294     */
295    protected void setTunnelStoppingDistance(int distance){
296        c3Label.setVisible(distance>0);
297        c3Label.setText(String.valueOf(distance));
298    }
299
300    private void setBg(JComponent p){
301        p.setBackground(DmiPanel.BACKGROUND_COLOUR);
302        p.setBorder(javax.swing.BorderFactory.createLineBorder(Color.black, 1));
303    }
304
305    protected void setC8Label(Icon ico) {
306        c8Label.setIcon(ico);
307    }
308
309    protected void setLevel(int level){
310        switch (level) {
311            case -1: // NTC
312                setC8Label(ResourceUtil.getImageIcon("LE_02.bmp"));
313                break;
314            case 0:
315                setC8Label(ResourceUtil.getImageIcon("LE_01.bmp"));
316                break;
317            case 1:
318                setC8Label(ResourceUtil.getImageIcon("LE_03.bmp"));
319                break;
320            case 2:
321                setC8Label(ResourceUtil.getImageIcon("LE_04.bmp"));
322                break;
323            case 3:
324                setC8Label(ResourceUtil.getImageIcon("LE_05.bmp"));
325                break;
326            default:
327                setC8Label(null);
328                break;
329        }
330    }
331
332    protected void setIntervetionSymbol( boolean newVal) {
333        c9Label.setIcon(newVal ? ResourceUtil.getImageIcon("ST_01.bmp") : null);
334    }
335
336    protected void setReversingPermittedSymbol( boolean newVal) {
337        c6Label.setIcon(newVal ? ResourceUtil.getImageIcon("ST_06.bmp") : null);
338    }
339
340    private void changeC1Border(){
341        c1Button.setBorder( nextc1FlashState ? DmiPanel.BORDER_ACK : DmiPanel.BORDER_NORMAL);
342        nextc1FlashState = !nextc1FlashState;
343    }
344
345    private void changeC2Border(){
346        c2Button.setBorder( nextc2FlashState ? DmiPanel.BORDER_ACK : DmiPanel.BORDER_NORMAL);
347        nextc2FlashState = !nextc2FlashState;
348    }
349
350    private void acknowledgeLevelPressed(ActionEvent e){
351        log.debug("confirmation of level change {}", e);
352        mainPanel.removeFlashListener(pclC1, false);
353        nextc1FlashState = true;
354        c1Button.setBorder(DmiPanel.BORDER_NORMAL);
355        setModeAcknowledge(DmiPanel.MODE_NONE);
356        if ( e.getActionCommand().startsWith("Level") ) {
357            int newLevel = jmri.util.StringUtil.getFirstIntFromString(e.getActionCommand());
358            setC1LevelTransitionIcon(newLevel, false);
359        }
360        mainPanel.firePropertyChange(e.getActionCommand(), false, true);
361    }
362
363    private void acknowledgeTunnelStopPressed(ActionEvent e){
364        log.debug("confirmation of tunnel stopping area pressed {}", e);
365        mainPanel.removeFlashListener(pclC2, false);
366        c2Button.setBorder(DmiPanel.BORDER_NORMAL);
367        nextc1FlashState = true;
368        c2Button.setEnabled(false);
369        mainPanel.firePropertyChange(DmiPanel.PROP_CHANGE_TUNNEL_STOP_AREA_ACK, false, true);
370        
371    }
372
373    protected void dispose(){
374        mainPanel.removeFlashListener(pclC1, false);
375        mainPanel.removeFlashListener(pclC2, false);
376    }
377
378    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DmiPanelC.class);
379
380}