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}