001package jmri.jmrit.etcs.dmi.swing; 002 003import jmri.jmrit.etcs.CabMessage; 004import jmri.jmrit.etcs.ResourceUtil; 005 006import java.awt.*; 007import java.awt.event.*; 008import java.text.SimpleDateFormat; 009import java.util.*; 010import java.util.List; 011 012import javax.annotation.Nonnull; 013import javax.swing.*; 014 015/** 016 * Class for ERTMS DMI Panel E, the Driver Messages area. 017 * @author Steve Young Copyright (C) 2024 018 */ 019public class DmiPanelE extends JPanel { 020 021 private final JButton e10upArrow; 022 private final JButton e11downArrow; 023 024 private final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm"); 025 026 private final JLabel labele1; 027 private final DmiPanel mainPane; 028 private final JPanel messagePanel; 029 private final JButton messageButton; 030 private int msgScroll = 0; 031 private final List<DmiCabMessage> messageList = new ArrayList<>(); 032 private final JLabel[] messageLabels; 033 private final JLabel[] timeLabels; 034 035 private final Font timeFont = new Font(DmiPanel.FONT_NAME, Font.PLAIN, 12); 036 private final Font messageFont = new Font(DmiPanel.FONT_NAME, Font.PLAIN, 16); 037 038 private CabMessage cabMessageBeingConfirmed; 039 040 private final JLabel msglabel1 = new JLabel(); 041 private final JLabel msglabel2 = new JLabel(); 042 private final JLabel msglabel3 = new JLabel(); 043 private final JLabel msglabel4 = new JLabel(); 044 private final JLabel msglabel5 = new JLabel(); 045 046 private final JLabel timeLabel1 = new JLabel(); 047 private final JLabel timeLabel2 = new JLabel(); 048 private final JLabel timeLabel3 = new JLabel(); 049 private final JLabel timeLabel4 = new JLabel(); 050 private final JLabel timeLabel5 = new JLabel(); 051 052 public DmiPanelE(@Nonnull DmiPanel mainPanel){ 053 super(); 054 055 mainPane = mainPanel; 056 messageButton = new JButton(); 057 058 setLayout(null); // Set the layout manager to null 059 060 setBackground(DmiPanel.BACKGROUND_COLOUR); 061 setBounds(0, 365, 334, 100); 062 063 JPanel e1 = new JPanel(); 064 JPanel e2 = new JPanel(); 065 JPanel e3 = new JPanel(); 066 JPanel e4 = new JPanel(); 067 messagePanel = initMsgPanel(); 068 069 e10upArrow = new JButton(); 070 e11downArrow = new JButton(); 071 072 e10upArrow.setFocusable(false); 073 e11downArrow.setFocusable(false); 074 075 e1.setBounds(0, 0, 54, 25); 076 e1.setBorder(BorderFactory.createLineBorder(Color.black, 1)); 077 e1.setBackground(DmiPanel.BACKGROUND_COLOUR); 078 079 e2.setBounds(0, 25, 54, 25); 080 e2.setBorder(BorderFactory.createLineBorder(Color.black, 1)); 081 e2.setBackground(DmiPanel.BACKGROUND_COLOUR); 082 083 e3.setBounds(0, 50, 54, 25); 084 e3.setBorder(BorderFactory.createLineBorder(Color.black, 1)); 085 e3.setBackground(DmiPanel.BACKGROUND_COLOUR); 086 087 e4.setBounds(0, 75, 54, 25); 088 e4.setBorder(BorderFactory.createLineBorder(Color.black, 1)); 089 e4.setBackground(DmiPanel.BACKGROUND_COLOUR); 090 091 messagePanel.setBounds(0, 0, 234, 100); 092 messagePanel.setBorder(BorderFactory.createLineBorder(Color.black, 1)); 093 messagePanel.setBackground(DmiPanel.BACKGROUND_COLOUR); 094 095 messageButton.setBounds(54, 0, 234, 100); 096 messageButton.setLayout(null); 097 messageButton.setContentAreaFilled(false); // Make the button transparent 098 messageButton.setBorderPainted(false); // Remove button border 099 messageButton.setName("messageAcknowledgeButton"); 100 messageButton.add(messagePanel); 101 messageButton.addActionListener(this::acknowledgeButtonPressed); 102 103 e10upArrow.setBounds(234+54, 0, 46, 50); 104 e10upArrow.setBorder(BorderFactory.createLineBorder(Color.black, 1)); 105 e10upArrow.setBackground(DmiPanel.BACKGROUND_COLOUR); 106 107 e11downArrow.setBounds(234+54, 50, 46, 50); 108 e11downArrow.setBorder(BorderFactory.createLineBorder(Color.black, 1)); 109 e11downArrow.setBackground(DmiPanel.BACKGROUND_COLOUR); 110 111 e10upArrow.setIcon(ResourceUtil.getImageIcon("NA_13.bmp")); 112 e11downArrow.setIcon(ResourceUtil.getImageIcon("NA_14.bmp")); 113 e10upArrow.setDisabledIcon(ResourceUtil.getImageIcon("NA_15.bmp")); 114 e11downArrow.setDisabledIcon(ResourceUtil.getImageIcon("NA_16.bmp")); 115 e10upArrow.setName("e10upArrow"); 116 e11downArrow.setName("e11downArrow"); 117 e10upArrow.addActionListener( (ActionEvent e) -> { msgScroll--; updateMsgPanel(); }); 118 e11downArrow.addActionListener( (ActionEvent e) -> { msgScroll++; updateMsgPanel(); }); 119 120 add(e1); 121 add(e2); 122 add(e3); 123 add(e4); 124 add(messageButton); 125 126 add(e10upArrow); 127 add(e11downArrow); 128 129 labele1 = new JLabel(); 130 e1.add(labele1); 131 132 timeLabels = new JLabel[]{timeLabel1,timeLabel2,timeLabel3,timeLabel4,timeLabel5}; 133 messageLabels = new JLabel[]{msglabel1,msglabel2,msglabel3,msglabel4,msglabel5}; 134 135 e10upArrow.setEnabled(false); 136 e11downArrow.setEnabled(false); 137 } 138 139 private void acknowledgeButtonPressed(ActionEvent e){ 140 setMessageButtonEnabled(false); 141 cabMessageBeingConfirmed.setConfirmed(); 142 mainPane.firePropertyChange(DmiPanel.PROP_CHANGE_CABMESSAGE_ACK, cabMessageBeingConfirmed.getMessageId()); 143 updateMsgPanel(); 144 } 145 146 private JPanel initMsgPanel() { 147 JPanel p = new JPanel(); 148 p.setLayout(null); 149 150 timeLabel1.setBounds(5, 0, 35, 20); 151 timeLabel2.setBounds(5, 20, 35, 20); 152 timeLabel3.setBounds(5, 40, 35, 20); 153 timeLabel4.setBounds(5, 60, 35, 20); 154 timeLabel5.setBounds(5,80, 35, 20); 155 156 msglabel1.setBounds(50, 0, 204, 20); 157 msglabel2.setBounds(50, 20, 204, 20); 158 msglabel3.setBounds(50, 40, 204, 20); 159 msglabel4.setBounds(50, 60, 204, 20); 160 msglabel5.setBounds(50, 80, 204, 20); 161 162 timeLabel1.setBackground(DmiPanel.BACKGROUND_COLOUR); 163 timeLabel2.setBackground(DmiPanel.BACKGROUND_COLOUR); 164 timeLabel3.setBackground(DmiPanel.BACKGROUND_COLOUR); 165 timeLabel4.setBackground(DmiPanel.BACKGROUND_COLOUR); 166 timeLabel5.setBackground(DmiPanel.BACKGROUND_COLOUR); 167 168 msglabel1.setBackground(DmiPanel.BACKGROUND_COLOUR); 169 msglabel2.setBackground(DmiPanel.BACKGROUND_COLOUR); 170 msglabel3.setBackground(DmiPanel.BACKGROUND_COLOUR); 171 msglabel4.setBackground(DmiPanel.BACKGROUND_COLOUR); 172 msglabel5.setBackground(DmiPanel.BACKGROUND_COLOUR); 173 174 timeLabel1.setForeground(Color.WHITE); 175 timeLabel2.setForeground(Color.WHITE); 176 timeLabel3.setForeground(Color.WHITE); 177 timeLabel4.setForeground(Color.WHITE); 178 timeLabel5.setForeground(Color.WHITE); 179 180 msglabel1.setForeground(Color.WHITE); 181 msglabel2.setForeground(Color.WHITE); 182 msglabel3.setForeground(Color.WHITE); 183 msglabel4.setForeground(Color.WHITE); 184 msglabel5.setForeground(Color.WHITE); 185 186 timeLabel1.setFont(timeFont); 187 timeLabel2.setFont(timeFont); 188 timeLabel3.setFont(timeFont); 189 timeLabel4.setFont(timeFont); 190 timeLabel5.setFont(timeFont); 191 192 msglabel1.setFont(messageFont); 193 msglabel2.setFont(messageFont); 194 msglabel3.setFont(messageFont); 195 msglabel4.setFont(messageFont); 196 msglabel5.setFont(messageFont); 197 198 msglabel1.setName("msglabel1"); 199 msglabel5.setName("msglabel5"); 200 timeLabel1.setName("timeLabel1"); 201 202 p.add(timeLabel1); 203 p.add(timeLabel2); 204 p.add(timeLabel3); 205 p.add(timeLabel4); 206 p.add(timeLabel5); 207 208 p.add(msglabel1); 209 p.add(msglabel2); 210 p.add(msglabel3); 211 p.add(msglabel4); 212 p.add(msglabel5); 213 214 return p; 215 } 216 217 protected void addMessage(@Nonnull CabMessage msg){ 218 // replace existing message with same ID 219 removeMessage(msg.getMessageId()); 220 messageList.add(new DmiCabMessage(msg, messageFont)); 221 msgScroll = 0; 222 updateMsgPanel(); 223 } 224 225 protected void removeMessage(String messageId){ 226 Iterator<DmiCabMessage> iterator = messageList.iterator(); 227 while (iterator.hasNext()) { 228 DmiCabMessage obj = iterator.next(); 229 if (obj.getMessageId().equals(messageId)) { 230 iterator.remove(); // Remove the object from the list 231 } 232 } 233 updateMsgPanel(); 234 } 235 236 private void updateMsgPanel(){ 237 // log.info("starting update"); 238 Comparator<CabMessage> customComparator = Comparator 239 .comparing(CabMessage::getAckRequired, Comparator.reverseOrder()) // Sort by boolean value (true first) 240 .thenComparingInt(CabMessage::getGroup) // Then sort by integer value (low to high) 241 .thenComparing(CabMessage::getSentTime, Comparator.reverseOrder()); // Then sort by time (newest first) 242 243 // Sort the list using the custom comparator 244 Collections.sort(messageList, customComparator); 245 246 // reset previous message display 247 for (int i = 0; i < 5; i++){ 248 messageLabels[i].setText(""); 249 timeLabels[i].setText(""); 250 } 251 252 if ( !messageList.isEmpty()) { 253 DmiCabMessage msg = messageList.get(0); 254 if ( msg.getAckRequired() ) { 255 setAckReqdMessage(msg); 256 return; 257 } 258 } 259 260 displayMessages(); 261 } 262 263 private void displayMessages() { 264 setMessageButtonEnabled(false); 265 266 List<String> tempTimes = new ArrayList<>(); 267 List<String> tempMessages = new ArrayList<>(); 268 for ( DmiCabMessage msg : messageList) { 269 log.debug("CabM: {}", msg); 270 String[] msgText = msg.getMessageArray(); 271 for (int i = 0; i < msgText.length; i++){ 272 tempTimes.add( i==0 ? dateFormat.format(msg.getSentTime().getTime()): ""); 273 tempMessages.add(msgText[i]); 274 } 275 } 276 277 msgScroll = Math.min(msgScroll, Math.max(tempMessages.size()-5, 0)); 278 for (int i = 0; i < 5; i++){ 279 int position = i + msgScroll; 280 if ( position < tempMessages.size() ) { 281 timeLabels[i].setText(tempTimes.get(position)); 282 messageLabels[i].setText(tempMessages.get(position)); 283 } 284 } 285 e10upArrow.setEnabled(msgScroll > 0); 286 e11downArrow.setEnabled(msgScroll < tempMessages.size()-5); 287 } 288 289 private void setAckReqdMessage(DmiCabMessage msg){ 290 log.debug("ack reqd, display single message"); 291 setMessageButtonEnabled(true); 292 293 timeLabels[0].setText(dateFormat.format(msg.getSentTime().getTime())); 294 cabMessageBeingConfirmed = msg; 295 296 String[] msgText = msg.getMessageArray(); 297 log.debug("formatted msg has {} lines", msgText.length); 298 299 for (int i = 0; i < 5; i++){ 300 if ( i < msgText.length ){ 301 log.debug("msgText {} {}", i, msgText[i]); 302 messageLabels[i].setText(msgText[i]); 303 } 304 } 305 e10upArrow.setEnabled(false); 306 e11downArrow.setEnabled(false); 307 } 308 309 private void setMessageButtonEnabled(boolean newVal) { 310 messageButton.setEnabled(newVal); 311 messagePanel.setBorder(BorderFactory.createLineBorder( 312 newVal ? DmiPanel.YELLOW : DmiPanel.BACKGROUND_COLOUR, 2)); 313 } 314 315 // 1 ok, 0 conn lost, -1 not visible 316 protected void setSafeRadioConnection(int newVal) { 317 switch (newVal) { 318 case 1: 319 labele1.setIcon(ResourceUtil.getImageIcon("ST_03.bmp")); 320 labele1.setToolTipText(Bundle.getMessage("RadioConnectionOK")); 321 break; 322 case 0: 323 labele1.setIcon(ResourceUtil.getImageIcon("ST_04.bmp")); 324 labele1.setToolTipText(Bundle.getMessage("RadioConnectionLost")); 325 break; 326 default: 327 labele1.setIcon(null); 328 labele1.setToolTipText(null); 329 break; 330 } 331 } 332 333 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DmiPanelE.class); 334 335}