001package jmri.jmrix.dccpp.swing.mon; 002 003import jmri.jmrix.dccpp.DCCppListener; 004import jmri.jmrix.dccpp.DCCppMessage; 005import jmri.jmrix.dccpp.DCCppReply; 006import jmri.jmrix.dccpp.DCCppSystemConnectionMemo; 007import jmri.jmrix.dccpp.DCCppTrafficController; 008import jmri.jmrix.dccpp.serial.SerialDCCppPacketizer; 009 010import java.awt.BorderLayout; 011import java.awt.Dimension; 012import java.awt.FlowLayout; 013import java.awt.event.ActionEvent; 014import java.awt.event.ComponentAdapter; 015import java.awt.event.ComponentEvent; 016 017import javax.swing.*; 018 019import jmri.util.swing.TextAreaFIFO; 020import jmri.util.swing.WrapLayout; 021 022/** 023 * Frame displaying (and logging) DCCpp command messages. 024 * 025 * @author Bob Jacobsen Copyright (C) 2001 026 * @author Chuck Catania Copyright (C) 2014, 2016, 2017 027 * @author mstevetodd Copyright (C) 2021 028 */ 029public class DCCppMonFrame extends jmri.jmrix.AbstractMonFrame implements DCCppListener { 030 031 // member declarations 032 final java.util.ResourceBundle rb = java.util.ResourceBundle.getBundle("jmri.jmrix.dccpp.swing.DCCppSwingBundle"); // NOI18N 033 034 private DCCppTrafficController tc = null; 035 private DCCppSystemConnectionMemo _memo = null; 036 037 private SerialDCCppPacketizer serialDCCppTC = null; 038 039 private final JPanel serialPane = new JPanel(); 040 private final JLabel queuedEntriesLabel = new JLabel("", SwingConstants.LEFT); // NOI18N 041 private final JToggleButton pauseRefreshButton = new JToggleButton(); 042 private final JButton clearRefreshQueueButton = new JButton(); 043 private final JCheckBox displayTranslatedCheckBox = new JCheckBox(Bundle.getMessage("ButtonShowTranslation")); 044 045 private final String doNotDisplayTranslatedCheck = this.getClass().getName() + ".DoNotDisplayTranslated"; // NOI18N 046 047 public DCCppMonFrame(DCCppSystemConnectionMemo memo) { 048 super(); 049 _memo = memo; 050 // Match DCC-EX's native <...> command syntax for the raw display. 051 rawOpenBracket = "<"; 052 rawCloseBracket = ">"; 053 // DCC-EX startup enumerates all turnouts/outputs one at a time, which 054 // easily exceeds the default 500-line limit on large layouts. 055 monTextPane = new TextAreaFIFO(2000); 056 } 057 058 @Override 059 protected String title() { 060 return rb.getString("DCCppMonFrameTitle")+" (" + _memo.getSystemPrefix() + ")"; // NOI18N 061 } 062 063 @Override 064 protected void init() { 065 066 // connect to TrafficController 067 tc = _memo.getDCCppTrafficController(); 068 tc.addDCCppListener(~0, this); 069 070 if ((tc instanceof SerialDCCppPacketizer) && tc.getCommandStation().isFunctionRefreshRequired()) { 071 serialDCCppTC = (SerialDCCppPacketizer) tc; 072 073 pauseRefreshButton.setSelected(!serialDCCppTC.isActiveRefresh()); 074 075 refreshQueuedMessages(); 076 077 add(serialPane, BorderLayout.PAGE_END); 078 } 079 080 // Create the background function refreshing-related buttons and add 081 // them to a panel. Will be hidden if not required by command station. 082 final JLabel functionLabel = new JLabel(Bundle.getMessage("LabelFunctionRefresh"), SwingConstants.LEFT); // NOI18N 083 084 pauseRefreshButton.setText(Bundle.getMessage("ButtonPauseRefresh")); // NOI18N 085 pauseRefreshButton.setVisible(true); 086 pauseRefreshButton.setToolTipText(Bundle.getMessage("TooltipPauseRefresh")); // NOI18N 087 // the selected state of pauseRefreshButton will be set when the context 088 // is created 089 090 clearRefreshQueueButton.setText(Bundle.getMessage("ButtonClearRefreshQueue")); // NOI18N 091 clearRefreshQueueButton.setVisible(true); 092 clearRefreshQueueButton.setToolTipText(Bundle.getMessage("TooltipClearRefreshQueue")); // NOI18N 093 094 serialPane.setLayout(new BoxLayout(serialPane, BoxLayout.LINE_AXIS)); 095 serialPane.add(functionLabel); 096 serialPane.add(Box.createRigidArea(new Dimension(5, 0))); 097 serialPane.add(pauseRefreshButton); 098 serialPane.add(Box.createRigidArea(new Dimension(5, 0))); 099 serialPane.add(clearRefreshQueueButton); 100 serialPane.add(Box.createRigidArea(new Dimension(5, 0))); 101 serialPane.add(queuedEntriesLabel); 102 103 pauseRefreshButton.addActionListener((final java.awt.event.ActionEvent e) -> { 104 pauseButtonEvent(e); 105 }); 106 107 clearRefreshQueueButton.addActionListener((final java.awt.event.ActionEvent e) -> { 108 clearButtonEvent(e); 109 }); 110 111 pack(); 112 } 113 114 /** 115 * Define system-specific help item. 116 */ 117// @Override 118// protected void setHelp() { 119// addHelpMenu("package.jmri.jmrix.DCCpp.DCCpp.DCCppmon.DCCppMonFrame", true); // NOI18N 120// } 121 122 //------------------- 123 // Transmit Packets 124 //------------------- 125 @Override 126 public synchronized void message(DCCppMessage l) { 127 128 // display the decoded data 129 StringBuilder text = new StringBuilder(); 130 if ( displayTranslatedCheckBox.isSelected() ) { 131 text.append(l.toMonitorString()); 132 } 133 text.append("\n"); 134 135 nextLine(text.toString(), (rawCheckBox.isSelected() ? l.toString() : ""), "TX:"); 136 refreshQueuedMessages(); 137 138 } 139 140 @Override 141 public void message(DCCppReply l) { 142 // receive a DCC-EX message and log it 143 // display the raw data if requested 144 if (log.isDebugEnabled()) { 145 log.debug("Message in Monitor: '{}' opcode {}", l, Character.toString(l.getOpCodeChar())); 146 } 147 148 StringBuilder text = new StringBuilder(); 149 if ( displayTranslatedCheckBox.isSelected() ) { 150 text.append(l.toMonitorString()); 151 } 152 text.append("\n"); 153 154 nextLine(text.toString(), (rawCheckBox.isSelected() ? l.toString() : ""), "RX:"); 155 156 //enable or disable the refresh pane based on support by command station 157 if (l.isStatusReply()) { 158 if (tc.getCommandStation().isFunctionRefreshRequired()) { 159 serialPane.setVisible(true); 160 } else { 161 serialPane.setVisible(false); 162 } 163 } 164 165 } 166 167 @Override 168 public void notifyTimeout(DCCppMessage msg) { 169 // TODO Auto-generated method stub 170 171 } 172 173 private void clearButtonEvent(final ActionEvent e) { 174 if (serialDCCppTC != null) 175 serialDCCppTC.clearRefreshQueue(); 176 177 refreshQueuedMessages(); 178 } 179 180 private void pauseButtonEvent(final ActionEvent e) { 181 final JToggleButton source = (JToggleButton) e.getSource(); 182 183 if (serialDCCppTC != null) 184 serialDCCppTC.setActiveRefresh(!source.isSelected()); 185 } 186 187 private int previouslyQueuedMessages = -1; 188 189 public synchronized void refreshQueuedMessages() { 190 if (serialDCCppTC != null) { 191 final int currentlyQueuedMessages = serialDCCppTC.getQueueLength(); 192 193 if (currentlyQueuedMessages != previouslyQueuedMessages) { 194 queuedEntriesLabel.setText(Bundle.getMessage("LabelQueuedEntries", String.valueOf(currentlyQueuedMessages))); // NOI18N 195 196 clearRefreshQueueButton.setEnabled(currentlyQueuedMessages > 0); 197 198 previouslyQueuedMessages = currentlyQueuedMessages; 199 } 200 } 201 } 202 203 private void displayTranslatedEvent(final ActionEvent e) { 204 if ( neitherCheckBoxSelected() ) { 205 rawCheckBox.setSelected(true); 206 } 207 } 208 209 private void rawCheckBoxEvent(final ActionEvent e) { 210 if ( neitherCheckBoxSelected() ) { 211 displayTranslatedCheckBox.setSelected(true); 212 } 213 } 214 215 private boolean neitherCheckBoxSelected() { 216 return (!( displayTranslatedCheckBox.isSelected()) ) && (!(rawCheckBox.isSelected())); 217 } 218 219 @Override 220 protected JPanel getActionButtonsPanel() { 221 // Combine action + log buttons into one wrapping row so they stay grouped. 222 super.getActionButtonsPanel(); // wire listeners/tooltips on the shared buttons 223 super.getLogToFilePanel(); 224 JPanel p = new JPanel(new WrapLayout(FlowLayout.CENTER, 5, 0)); 225 p.add(clearButton); 226 p.add(freezeButton); 227 p.add(openFileChooserButton); 228 p.add(startLogButton); 229 p.add(stopLogButton); 230 return p; 231 } 232 233 @Override 234 protected JPanel getLogToFilePanel() { 235 return new JPanel(); // log buttons live in getActionButtonsPanel 236 } 237 238 @Override 239 public JPanel getCheckBoxPanel(){ 240 super.getCheckBoxPanel(); // wire listeners/tooltips on the shared checkboxes 241 displayTranslatedCheckBox.addActionListener(this::displayTranslatedEvent); 242 rawCheckBox.addActionListener(this::rawCheckBoxEvent); 243 displayTranslatedCheckBox.setSelected(!userPrefs.getSimplePreferenceState(doNotDisplayTranslatedCheck)); 244 rawCheckBoxEvent(null); // if neither raw nor translated selected, force translated on. 245 246 JPanel p = new JPanel(new WrapLayout(FlowLayout.CENTER, 5, 0)); 247 p.add(displayTranslatedCheckBox); 248 p.add(rawCheckBox); 249 p.add(timeCheckBox); 250 p.add(alwaysOnTopCheckBox); 251 p.add(autoScrollCheckBox); 252 return p; 253 } 254 255 @Override 256 protected boolean useStackedControlsLayout() { 257 return true; 258 } 259 260 @Override 261 public void initComponents() { 262 super.initComponents(); 263 // BoxLayout's first pass after a resize computes child preferred-heights 264 // with the stale widths, so WrapLayout rows can be one pass out of date. 265 // Defer a second revalidate to settle layout using the new widths. 266 addComponentListener(new ComponentAdapter() { 267 @Override 268 public void componentResized(ComponentEvent e) { 269 SwingUtilities.invokeLater(DCCppMonFrame.this::revalidate); 270 } 271 }); 272 } 273 274 @Override 275 public void dispose() { 276 if ( tc != null ) { 277 tc.removeDCCppListener(~0, this); 278 } 279 if (userPrefs!=null) { 280 userPrefs.setSimplePreferenceState(doNotDisplayTranslatedCheck, !displayTranslatedCheckBox.isSelected()); 281 } 282 super.dispose(); 283 } 284 285 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DCCppMonFrame.class); 286 287}