001package jmri.jmrit.etcs.dmi.swing;
002
003import java.awt.Color;
004import java.awt.Font;
005import java.awt.event.ActionEvent;
006import java.beans.PropertyChangeEvent;
007import java.beans.PropertyChangeListener;
008
009import java.util.List;
010
011import javax.annotation.CheckForNull;
012import javax.annotation.Nonnull;
013import javax.swing.*;
014
015import jmri.jmrit.etcs.TrackCondition;
016import jmri.jmrit.etcs.ResourceUtil;
017
018import org.apiguardian.api.API;
019
020/**
021 * Class to demonstrate features of ERTMS DMI Panel B,
022 * Speedometer Dial and Buttons underneath.
023 * @author Steve Young Copyright (C) 2024
024 */
025@API(status=API.Status.EXPERIMENTAL)
026public class DmiPanelB extends JPanel {
027
028    private final JLabel b6Label;
029    private final JLabel b7Label;
030    private final JLabel b8Label;
031    private final DmiSpeedoDialPanel p;
032
033    private final UnderDialButton b3;
034    private final UnderDialButton b4;
035    private final UnderDialButton b5;
036
037    private final List<TrackCondition> requireAllocationOrderAccouncements;
038    private final List<UnderDialButton> underDialButtonList;
039
040    public DmiPanelB(@Nonnull DmiPanel main){
041        super();
042        setLayout(null);
043        setBackground(DmiPanel.BACKGROUND_COLOUR);
044        setBounds(54, 15, 280, 300);
045
046        setOpaque(true);
047        p = new DmiSpeedoDialPanel();
048
049        requireAllocationOrderAccouncements = new java.util.ArrayList<>();
050        underDialButtonList = new java.util.ArrayList<>();
051
052        b3 = new UnderDialButton(main);
053        b4 = new UnderDialButton(main);
054        b5 = new UnderDialButton(main);
055
056        underDialButtonList.add(b3);
057        underDialButtonList.add(b4);
058        underDialButtonList.add(b5);
059
060        JPanel b6 = new JPanel();
061        JPanel b7 = new JPanel();
062        JPanel b8 = new JPanel();
063
064        p.setBounds(0, 0, 280, 300);
065        b3.setBounds(122-36, 256, 36, 36);
066        b4.setBounds(122, 256, 36, 36);
067        b5.setBounds(122+36, 256, 36, 36);
068        b6.setBounds(10, 256, 36, 36);
069        b7.setBounds(254-18, 256, 36, 36);
070        b8.setBounds(140-18, 216-18, 36, 36);
071
072        setBg(b6);
073        setBg(b7);
074        setBg(b8);
075
076        // b3, b4 and b5 are shared in that when b3 is occupied, b4 is used, then b5.
077        // if further info needs to be displayed, wait until free slot.
078
079        b6.setToolTipText("Release Speed");
080
081        b6Label = new JLabel();
082        b6Label.setForeground(DmiPanel.MEDIUM_GREY);
083        b6Label.setFont(new Font(DmiPanel.FONT_NAME, Font.BOLD, 22));
084        b6Label.setBounds(0, 0, 36, 36);
085        b6Label.setVerticalAlignment(SwingConstants.CENTER);
086        b6.add(b6Label);
087
088        b7Label = new JLabel();
089        b7.add(b7Label);
090
091        add(p);
092        add(b3);
093        add(b4);
094        add(b5);
095        add(b6);
096        add(b7);
097
098        b8Label = new JLabel();
099        b8.add(b8Label);
100
101        add(b8);
102
103        DmiPanelB.this.setMode(13); // Standby Mode
104    }
105
106    protected void addAnnouncement( TrackCondition tc ){
107        log.debug("adding announcement {}", tc);
108        requireAllocationOrderAccouncements.add(tc);
109        updateDisplayOrderAccouncements();
110    }
111
112    protected void removeAnnouncement( TrackCondition tc ){
113        log.debug("b4 remove {}", requireAllocationOrderAccouncements.size());
114        requireAllocationOrderAccouncements.remove(tc);
115        removeFromButton(tc);
116        updateDisplayOrderAccouncements();
117        log.debug("after remove {}", requireAllocationOrderAccouncements.size());
118    }
119
120    private void removeFromButton(TrackCondition tc) {
121        underDialButtonList.forEach( udb -> {
122            if ( tc.equals(udb.getTrackCondition())) {
123                log.debug("removing track condition {}", tc);
124                udb.setTrackCondition(null);
125            } });
126    }
127
128    private void updateDisplayOrderAccouncements(){
129        if ( !requireAllocationOrderAccouncements.isEmpty() ) {
130            for ( UnderDialButton udb : underDialButtonList){
131                log.debug("updateDisplayOrderAccouncements for {}", udb.getTrackCondition() );
132                if ( udb.getTrackCondition() == null ) {
133                    log.debug("setting tc to {}", requireAllocationOrderAccouncements.get(0));
134                    udb.setTrackCondition(requireAllocationOrderAccouncements.remove(0));
135                    return;
136                }
137            }
138        }
139    }
140
141    private void setBg(JPanel p){
142        p.setBackground(DmiPanel.BACKGROUND_COLOUR);
143        p.setBorder(BorderFactory.createLineBorder(Color.black, 1));
144    
145    }
146
147    protected void setMaxDialSpeed( int speed ) {
148        p.setMaxDialSpeed( speed);
149    }
150
151    protected void setCentreCircleAndDialColor ( Color color ) {
152        p.setCentreCircleAndDialColor(color);
153    }
154
155    protected void setActualSpeed( float speed ) {
156        p.update(speed);
157    }
158
159    protected void setTargetAdviceSpeed(int newVal){
160        p.setTargetAdviceSpeed(newVal);
161    }
162
163    protected void setCsgSections(List<DmiCircularSpeedGuideSection> list){
164        p.setCsgSections(list);
165    }
166
167    protected void setDisplaySpeedUnit( String newVal ) {
168        p.setDisplaySpeedUnit(newVal);
169    }
170
171    protected void setReleaseSpeed(int spd){
172        b6Label.setText(spd<0 ? "" : String.valueOf(spd));
173    }
174
175    protected void setReleaseSpeedColour(Color newColour){
176        b6Label.setForeground(newColour);
177    }
178
179    /**
180     * Set Mode.
181     * 0 - No Mode Displayed
182     * 1 - Shunting
183     * 4 - Trip
184     * 6 - Post Trip
185     * 7 - On Sight
186     * 9 - Staff Responsible
187     * 11 - Full Supervision Mode
188     * 12 - Non-leading
189     * 13 - Standby
190     * 14 - Reversing
191     * 16 - Unfitted
192     * 18 - System Failure
193     * 21 - Limited Supervision - Not ERTMS4
194     * 23 - Automatic Driving ( From ERTMS4 )
195     * 24 - Supervised Manoeuvre ( From ERTMS4 )
196     * @param newMode The Mode to display in B6.
197     */
198    protected void setMode(int newMode){
199        b7Label.setVisible( newMode != 0 );
200        setCoasting(false);
201        setSupervisedDirection(0);
202        switch (newMode) {
203            case 0:
204                break;
205            case 1:
206                b7Label.setIcon(ResourceUtil.getImageIcon("MO_01.bmp"));
207                b7Label.setToolTipText(Bundle.getMessage("Shunting"));
208                break;
209            case 4:
210                b7Label.setIcon(ResourceUtil.getImageIcon("MO_04.bmp"));
211                b7Label.setToolTipText(Bundle.getMessage("Trip"));
212                break;
213            case 6:
214                b7Label.setIcon(ResourceUtil.getImageIcon("MO_06.bmp"));
215                b7Label.setToolTipText(Bundle.getMessage("PostTrip"));
216                break;
217            case 7:
218                b7Label.setIcon(ResourceUtil.getImageIcon("MO_07.bmp"));
219                b7Label.setToolTipText(Bundle.getMessage("OnSight"));
220                break;
221            case 9:
222                b7Label.setIcon(ResourceUtil.getImageIcon("MO_09.bmp"));
223                b7Label.setToolTipText(Bundle.getMessage("StaffResponsible"));
224                break;
225            case 11:
226                b7Label.setIcon(ResourceUtil.getImageIcon("MO_11.bmp"));
227                b7Label.setToolTipText(Bundle.getMessage("FullSupervision"));
228                break;
229            case 12:
230                b7Label.setIcon(ResourceUtil.getImageIcon("MO_12.bmp"));
231                b7Label.setToolTipText(Bundle.getMessage("NonLeading"));
232                break;
233            case 13:
234                b7Label.setIcon(ResourceUtil.getImageIcon("MO_13.bmp"));
235                b7Label.setToolTipText(Bundle.getMessage("StandBy"));
236                break;
237            case 14:
238                b7Label.setIcon(ResourceUtil.getImageIcon("MO_14.bmp"));
239                b7Label.setToolTipText(Bundle.getMessage("Reversing"));
240                break;
241            case 16:
242                b7Label.setIcon(ResourceUtil.getImageIcon("MO_16.bmp"));
243                b7Label.setToolTipText(Bundle.getMessage("Unfitted"));
244                break;
245            case 18:
246                b7Label.setIcon(ResourceUtil.getImageIcon("MO_18.bmp"));
247                b7Label.setToolTipText(Bundle.getMessage("SystemFailure"));
248                break;
249            case 21:
250                b7Label.setIcon(ResourceUtil.getImageIcon("MO_21.bmp"));
251                b7Label.setToolTipText(Bundle.getMessage("LimitedSupervision"));
252                break;
253            case 23:
254                b7Label.setIcon(ResourceUtil.getImageIcon("MO_23.bmp"));
255                b7Label.setToolTipText(Bundle.getMessage("AutomaticDriving"));
256                break;
257            case 24:
258                b7Label.setIcon(ResourceUtil.getImageIcon("MO_24.bmp"));
259                b7Label.setToolTipText(Bundle.getMessage("SupervisedManoeuvre"));
260                break;
261            default:
262                log.error("Could not set Mode {}", newMode);
263        }
264    }
265
266    protected void setCoasting(boolean visible){
267        b8Label.setVisible(visible);
268        if ( visible ) {
269            b8Label.setIcon(ResourceUtil.getImageIcon("ATO_20.bmp"));
270            b8Label.setToolTipText(Bundle.getMessage("Coasting"));
271        } else {
272            b8Label.setIcon(null);
273            b8Label.setToolTipText(null);
274        }
275        b8Label.repaint();
276    }
277
278    // -1 reverse, 0 hidden, 1 forwards
279    protected void setSupervisedDirection(int newDirection) {
280        switch (newDirection) {
281            case -1:
282                b8Label.setIcon(ResourceUtil.getImageIcon("SM02.bmp"));
283                b8Label.setToolTipText(Bundle.getMessage("Reverse"));
284                break;
285            case 1:
286                b8Label.setIcon(ResourceUtil.getImageIcon("SM01.bmp"));
287                b8Label.setToolTipText(Bundle.getMessage("Forward"));
288                break;
289            default:
290            case 0:
291                b8Label.setIcon(null);
292                b8Label.setToolTipText(null);
293                break;
294        }
295        b8Label.setVisible(newDirection != 0);
296    }
297
298    private static class UnderDialButton extends JButton {
299
300        private final transient PropertyChangeListener pcl = (PropertyChangeEvent evt) ->  changeBorder(); 
301        private boolean nextFlashState = true;
302        private final DmiPanel mainPanel;
303
304        UnderDialButton(DmiPanel main){
305            super();
306            mainPanel = main;
307            setBorder(DmiPanel.BORDER_NORMAL);
308            setFocusable(false);
309            setBackground(DmiPanel.BACKGROUND_COLOUR);
310            addActionListener(this::buttonClicked);
311        }
312
313        void buttonClicked(ActionEvent e){
314            setEnabled(false);
315            mainPanel.removeFlashListener(pcl, false);
316            log.debug("button clicked: {}", e.getActionCommand());
317            mainPanel.firePropertyChange(e.getActionCommand(), false, true);
318            setBorder(DmiPanel.BORDER_NORMAL);
319        }
320
321        private TrackCondition tc = null;
322
323        void setTrackCondition(@CheckForNull TrackCondition newTc){
324            log.debug("button set track condition to {}", tc);
325            tc = newTc;
326            setEnabled(tc != null && tc.getIsOrder());
327            resetImage();
328            setActionCommand(newTc == null ? "": newTc.getAckString());
329            log.debug("set {} actionCommand to {}", tc,  getActionCommand());
330        }
331
332        void resetImage(){
333            if ( tc == null ) {
334                this.setIcon(null);
335                setBorder( DmiPanel.BORDER_NORMAL);
336                mainPanel.removeFlashListener(pcl, false);
337            } else {
338                setIcon((tc.getLargeIcon(isEnabled())));
339                if (isEnabled()){
340                    mainPanel.addFlashListener(pcl, false);
341                    nextFlashState = true;
342                    changeBorder();
343                }
344            }
345            this.repaint();
346        }
347
348        TrackCondition getTrackCondition(){
349            return tc;
350        }
351
352        private void changeBorder(){
353            setBorder( nextFlashState ? DmiPanel.BORDER_ACK : DmiPanel.BORDER_NORMAL);
354            nextFlashState = !nextFlashState;
355        }
356    }
357
358    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DmiPanelB.class);
359
360}