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}