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}