001package jmri.jmrix.can.cbus.swing.console; 002 003import java.awt.BorderLayout; 004import java.awt.Color; 005import java.awt.event.ActionEvent; 006import java.util.concurrent.ConcurrentLinkedDeque; 007 008import javax.swing.*; 009import javax.swing.text.BadLocationException; 010import javax.swing.text.DefaultHighlighter; 011import javax.swing.text.Highlighter; 012 013import jmri.jmrix.can.CanSystemConnectionMemo; 014import jmri.jmrix.can.TrafficController; 015import jmri.jmrix.can.cbus.CbusConfigurationManager; 016import jmri.jmrix.can.cbus.eventtable.CbusEventTableDataModel; 017import jmri.jmrix.can.cbus.swing.CbusEventHighlightFrame; 018import jmri.jmrix.can.cbus.swing.CbusSendEventPane; 019import jmri.util.ThreadingUtil; 020import jmri.util.swing.TextAreaFIFO; 021 022/** 023 * Frame for CBUS Console 024 * 025 * @author Andrew Crosland Copyright (C) 2008 026 * @author Steve Young Copyright (C) 2018 027 */ 028public class CbusConsolePane extends jmri.jmrix.can.swing.CanPanel { 029 030 static int console_instance_num; 031 static final private int MAX_LINES = 5000; 032 033 private final ConcurrentLinkedDeque<CbusConsoleLogEntry> logBuffer; 034 035 private JToggleButton freezeButton; 036 037 public TextAreaFIFO monTextPaneCan; 038 public TextAreaFIFO monTextPaneCbus; 039 private Highlighter cbusHighlighter; 040 private Highlighter canHighlighter; 041 042 protected final CbusConsoleStatsPane statsPane; 043 protected final CbusConsolePacketPane packetPane; 044 protected final CbusSendEventPane sendPane; 045 protected CbusConsoleDecodeOptionsPane decodePane; 046 protected final CbusConsoleLoggingPane logPane; 047 public final CbusConsoleDisplayOptionsPane displayPane; 048 049 // members for handling the CBUS interface 050 protected TrafficController tc; 051 052 public CbusConsolePane() { 053 super(); 054 incrementInstance(); 055 logBuffer = new ConcurrentLinkedDeque<>(); 056 statsPane = new CbusConsoleStatsPane(this); 057 packetPane = new CbusConsolePacketPane(this); 058 sendPane = new CbusSendEventPane(this); 059 logPane = new CbusConsoleLoggingPane(this); 060 displayPane = new CbusConsoleDisplayOptionsPane(this); 061 062 } 063 064 public static int getConsoleInstanceNum() { 065 return console_instance_num; 066 } 067 068 public static void incrementInstance() { 069 console_instance_num++; 070 } 071 072 /** 073 * {@inheritDoc} 074 */ 075 @Override 076 public String getTitle() { 077 if (memo != null) { 078 StringBuilder title = new StringBuilder(20); 079 title.append(memo.getUserName()).append(" "); 080 title.append(Bundle.getMessage("CbusConsoleTitle")); 081 if (getConsoleInstanceNum() > 1) { 082 title.append(" ").append( getConsoleInstanceNum() ); 083 } 084 return title.toString(); 085 } 086 return Bundle.getMessage("CbusConsoleTitle"); 087 } 088 089 /** 090 * {@inheritDoc} 091 */ 092 @Override 093 public String getHelpTarget() { 094 return "package.jmri.jmrix.can.cbus.swing.console.CbusConsoleFrame"; 095 } 096 097 /** 098 * {@inheritDoc} 099 */ 100 @Override 101 public void dispose() { 102 if (decodePane!=null) { 103 decodePane.dispose(); 104 } 105 displayPane.dispose(); 106 statsPane.dispose(); 107 super.dispose(); 108 } 109 110 /** 111 * {@inheritDoc} 112 */ 113 @Override 114 public void initComponents(CanSystemConnectionMemo memo) { 115 initComponents( memo, true); 116 } 117 118 /** 119 * Constructor For testing purposes, not for general use. 120 * @param memo System Connection 121 * @param launchEvTable true to launch a CBUS Event Table Model, else false. 122 */ 123 public void initComponents(CanSystemConnectionMemo memo, boolean launchEvTable) { 124 super.initComponents(memo); 125 tc = memo.getTrafficController(); 126 decodePane = new CbusConsoleDecodeOptionsPane(this); 127 if (launchEvTable){ 128 memo.get(CbusConfigurationManager.class).provide(CbusEventTableDataModel.class); 129 } 130 init(); 131 } 132 133 public void init() { 134 135 initTextAreas(); 136 137 // Sub-pane to hold buttons 138 JPanel paneA = new JPanel(); 139 paneA.setLayout(new BoxLayout(paneA, BoxLayout.Y_AXIS)); 140 paneA.add(getClearFreezeButtonPane()); 141 paneA.add(decodePane); 142 143 JPanel historyPane = new JPanel(); 144 historyPane.setLayout(new BorderLayout()); 145 historyPane.setBorder(BorderFactory.createTitledBorder( 146 BorderFactory.createEtchedBorder(), Bundle.getMessage("PacketHistoryTitle"))); 147 148 historyPane.add(getSplitPane(), BorderLayout.CENTER); 149 historyPane.add(paneA, BorderLayout.SOUTH); 150 151 setLayout(new BorderLayout()); 152 add(displayPane, BorderLayout.NORTH); 153 add(historyPane, BorderLayout.CENTER); 154 add(getAllBottomPanes(), BorderLayout.SOUTH); 155 displayPane.matchVisisbleToCheckBoxes(null); 156 157 } 158 159 private void initTextAreas() { 160 161 monTextPaneCan = new TextAreaFIFO(MAX_LINES); 162 monTextPaneCan.setVisible(true); 163 monTextPaneCan.setToolTipText(Bundle.getMessage("TooltipMonTextPaneCan")); 164 monTextPaneCan.setEditable(false); 165 monTextPaneCan.setRows(5); 166 monTextPaneCan.setColumns(5); 167 168 monTextPaneCbus = new TextAreaFIFO(MAX_LINES); 169 monTextPaneCbus.setVisible(true); 170 monTextPaneCbus.setToolTipText(Bundle.getMessage("TooltipMonTextPaneCbus")); 171 monTextPaneCbus.setEditable(false); 172 monTextPaneCbus.setRows(5); 173 monTextPaneCbus.setColumns(20); 174 175 cbusHighlighter = monTextPaneCbus.getHighlighter(); 176 canHighlighter = monTextPaneCan.getHighlighter(); 177 178 } 179 180 private JSplitPane getSplitPane(){ 181 182 JScrollPane jScrollPane1Can = new JScrollPane(); 183 jScrollPane1Can.getViewport().add(monTextPaneCan); 184 jScrollPane1Can.setVisible(true); 185 jScrollPane1Can.setBorder(BorderFactory.createTitledBorder( 186 BorderFactory.createEtchedBorder(), Bundle.getMessage("CanFrameTitle"))); 187 188 JScrollPane jScrollPane1Cbus = new JScrollPane(); 189 jScrollPane1Cbus.setBorder(BorderFactory.createTitledBorder( 190 BorderFactory.createEtchedBorder(), Bundle.getMessage("CbusMessageTitle"))); 191 jScrollPane1Cbus.getViewport().add(monTextPaneCbus); 192 jScrollPane1Cbus.setVisible(true); 193 194 jScrollPane1Can.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 195 jScrollPane1Cbus.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 196 jScrollPane1Can.setVerticalScrollBar(jScrollPane1Cbus.getVerticalScrollBar()); 197 198 // scroll panels to be side-by-side 199 JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, 200 jScrollPane1Can, jScrollPane1Cbus); 201 split.setResizeWeight(0.3); 202 split.setContinuousLayout(true); 203 204 return split; 205 } 206 207 private JPanel getClearFreezeButtonPane() { 208 209 JPanel messageButtonOptionpane = new JPanel(); 210 211 JButton clearButton = new JButton(); 212 freezeButton = new JToggleButton(); 213 214 clearButton.setText(Bundle.getMessage("ButtonClearScreen")); 215 clearButton.setToolTipText(Bundle.getMessage("ButtonClearLogTip")); 216 217 freezeButton.setText(Bundle.getMessage("ButtonFreezeScreen")); 218 freezeButton.setToolTipText(Bundle.getMessage("TooltipStopScroll")); 219 220 messageButtonOptionpane.setLayout(new BoxLayout(messageButtonOptionpane, BoxLayout.X_AXIS)); 221 messageButtonOptionpane.add(clearButton); 222 messageButtonOptionpane.add(freezeButton); 223 224 clearButton.addActionListener(this::clearButtonActionPerformed); 225 freezeButton.addActionListener(this::freezeButtonActionPerformed); 226 return messageButtonOptionpane; 227 228 } 229 230 private JPanel getAllBottomPanes() { 231 232 JPanel southPane = new JPanel(); 233 southPane.setLayout(new BoxLayout(southPane, BoxLayout.Y_AXIS)); 234 235 logPane.setVisible(false); 236 statsPane.setVisible(false); 237 packetPane.setVisible(false); 238 sendPane.setVisible(false); 239 240 southPane.add(logPane); 241 southPane.add(statsPane); 242 southPane.add(packetPane); 243 southPane.add(sendPane); 244 245 return southPane; 246 247 } 248 249 /** 250 * Handle display of traffic. 251 * @param line string the traffic in 'normal form', 252 * @param decoded string the decoded, protocol specific, form. 253 * Both should contain the same number of well-formed lines, e.g. end with \n 254 * @param highlight int 255 */ 256 public void nextLine(String line, String decoded, int highlight) { 257 258 logBuffer.add( new CbusConsoleLogEntry(line,decoded,highlight)); 259 260 // if not frozen, display it in the Swing thread 261 if (!freezeButton.isSelected()) { 262 ThreadingUtil.runOnGUIEventually( ()->{ 263 processLogBuffer(); 264 }); 265 } 266 267 // if requested, log to a file. 268 logPane.sendLogToFile( decoded ); 269 270 } 271 272 private void processLogBuffer() { 273 while (!logBuffer.isEmpty()){ 274 CbusConsoleLogEntry next = logBuffer.removeFirst(); 275 276 final int start = monTextPaneCbus.getText().length(); 277 final int startc= monTextPaneCan.getText().length(); 278 279 monTextPaneCan.append(next.getFrameText()); 280 monTextPaneCbus.append(next.getDecodedText()); 281 282 if (next.getHighlighter() > -1) { 283 try { 284 CbusHighlightPainter cbusHighlightPainter = new CbusHighlightPainter( 285 CbusEventHighlightFrame.highlightColors[next.getHighlighter()]); 286 // log.debug("Add highlight start: " + start + " end: " + end); 287 cbusHighlighter.addHighlight(start, monTextPaneCbus.getText().length() - 1, cbusHighlightPainter); 288 canHighlighter.addHighlight(startc, monTextPaneCan.getText().length() - 1, cbusHighlightPainter); 289 } catch (BadLocationException e) {} // do nothing 290 } 291 } 292 } 293 294 // clear the monitoring history 295 private void clearButtonActionPerformed(ActionEvent e) { 296 logBuffer.clear(); 297 monTextPaneCan.setText(""); 298 monTextPaneCbus.setText(""); 299 } 300 301 private void freezeButtonActionPerformed(ActionEvent e) { 302 if (freezeButton.isSelected()) { 303 freezeButton.setForeground(Color.red); 304 } else { 305 freezeButton.setForeground(new JTextField().getForeground()); // reset to default 306 nextLine("","",-1); // poke with zero content to refresh screen 307 } 308 } 309 310 // A private subclass of the default highlight painter 311 private class CbusHighlightPainter extends DefaultHighlighter.DefaultHighlightPainter { 312 protected CbusHighlightPainter(Color color) { 313 super(color); 314 } 315 } 316 317 /** 318 * Nested class to create one of these using old-style defaults. 319 */ 320 static public class Default extends jmri.jmrix.can.swing.CanNamedPaneAction { 321 322 public Default() { 323 super(Bundle.getMessage("CbusConsoleTitle"), 324 new jmri.util.swing.sdi.JmriJFrameInterface(), 325 CbusConsolePane.class.getName(), 326 jmri.InstanceManager.getDefault(CanSystemConnectionMemo.class)); 327 } 328 } 329 330 // private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusConsolePane.class); 331}