001package jmri.jmrix; 002 003import java.awt.Dimension; 004import java.awt.event.ActionEvent; 005import java.awt.event.ActionListener; 006import java.io.File; 007import java.io.FileOutputStream; 008import java.io.PrintStream; 009import java.text.DateFormat; 010import java.text.SimpleDateFormat; 011import java.util.Date; 012import javax.swing.BoxLayout; 013import javax.swing.JButton; 014import javax.swing.JCheckBox; 015import javax.swing.JFileChooser; 016import javax.swing.JPanel; 017import javax.swing.JScrollPane; 018import javax.swing.JTextArea; 019import javax.swing.JTextField; 020import javax.swing.JToggleButton; 021import jmri.util.FileUtil; 022import jmri.util.JmriJFrame; 023import jmri.util.swing.TextAreaFIFO; 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026import javax.annotation.OverridingMethodsMustInvokeSuper; 027 028/** 029 * Abstract base class for Frames displaying communications monitor information. 030 * 031 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2014 032 */ 033public abstract class AbstractMonFrame extends JmriJFrame { 034 035 // template functions to fill in 036 protected abstract String title(); // provide the title for the frame 037 038 /** 039 * Initialize the data source. 040 * <p> 041 * This is invoked at the end of the GUI initialization phase. Subclass 042 * implementations should connect to their data source here. 043 */ 044 protected abstract void init(); 045 046 // the subclass also needs a dispose() method to close any specific communications; call super.dispose() 047 @OverridingMethodsMustInvokeSuper 048 @Override 049 public void dispose() { 050 if (p!=null) { 051 p.setSimplePreferenceState(timeStampCheck, timeCheckBox.isSelected()); 052 p.setSimplePreferenceState(rawDataCheck, rawCheckBox.isSelected()); 053 p.setSimplePreferenceState(alwaysOnTopCheck, alwaysOnTopCheckBox.isSelected()); 054 p.setSimplePreferenceState(autoScrollCheck, !autoScrollCheckBox.isSelected()); 055 } 056 monTextPane.dispose(); 057 super.dispose(); 058 } 059 // you'll also have to add the message(Foo) members to handle info to be logged. 060 // these should call nextLine(String line, String raw) with their updates 061 062 // member declarations 063 protected JButton clearButton = new JButton(); 064 protected JToggleButton freezeButton = new JToggleButton(); 065 protected JScrollPane jScrollPane1 = new JScrollPane(); 066 protected TextAreaFIFO monTextPane = new TextAreaFIFO(MAX_LINES); 067 protected JButton startLogButton = new JButton(); 068 protected JButton stopLogButton = new JButton(); 069 protected JCheckBox rawCheckBox = new JCheckBox(); 070 protected JCheckBox timeCheckBox = new JCheckBox(); 071 protected JCheckBox alwaysOnTopCheckBox = new JCheckBox(); 072 protected JCheckBox autoScrollCheckBox = new JCheckBox(); 073 protected JButton openFileChooserButton = new JButton(); 074 protected JTextField entryField = new JTextField(); 075 protected JButton enterButton = new JButton(); 076 String rawDataCheck = this.getClass().getName() + ".RawData"; // NOI18N 077 String timeStampCheck = this.getClass().getName() + ".TimeStamp"; // NOI18N 078 String alwaysOnTopCheck = this.getClass().getName() + ".alwaysOnTop"; // NOI18N 079 String autoScrollCheck = this.getClass().getName() + ".AutoScroll"; // NOI18N 080 jmri.UserPreferencesManager p; 081 082 // for locking 083 AbstractMonFrame self; 084 085 // to find and remember the log file 086 public final javax.swing.JFileChooser logFileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath()); 087 088 public AbstractMonFrame() { 089 super(); 090 self = this; 091 } 092 093 /** 094 * {@inheritDoc} 095 */ 096 @Override 097 public void initComponents() { 098 099 p = jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class); 100 // the following code sets the frame's initial state 101 102 clearButton.setText(Bundle.getMessage("ButtonClearScreen")); // NOI18N 103 clearButton.setVisible(true); 104 clearButton.setToolTipText(Bundle.getMessage("TooltipClearMonHistory")); // NOI18N 105 106 freezeButton.setText(Bundle.getMessage("ButtonFreezeScreen")); // NOI18N 107 freezeButton.setVisible(true); 108 freezeButton.setToolTipText(Bundle.getMessage("TooltipStopScroll")); // NOI18N 109 110 enterButton.setText(Bundle.getMessage("ButtonAddMessage")); // NOI18N 111 enterButton.setVisible(true); 112 enterButton.setToolTipText(Bundle.getMessage("TooltipAddMessage")); // NOI18N 113 114 monTextPane.setVisible(true); 115 monTextPane.setToolTipText(Bundle.getMessage("TooltipMonTextPane")); // NOI18N 116 monTextPane.setEditable(false); 117 118 entryField.setToolTipText(Bundle.getMessage("TooltipEntryPane", Bundle.getMessage("ButtonAddMessage"))); // NOI18N 119 120 // fix a width for current character set 121 JTextField t = new JTextField(200); 122 int x = jScrollPane1.getPreferredSize().width + t.getPreferredSize().width; 123 int y = jScrollPane1.getPreferredSize().height + 10 * t.getPreferredSize().height; 124 125 jScrollPane1.getViewport().add(monTextPane); 126 jScrollPane1.setPreferredSize(new Dimension(x, y)); 127 jScrollPane1.setVisible(true); 128 129 startLogButton.setText(Bundle.getMessage("ButtonStartLogging")); // NOI18N 130 startLogButton.setVisible(true); 131 startLogButton.setToolTipText(Bundle.getMessage("TooltipStartLogging")); // NOI18N 132 133 stopLogButton.setText(Bundle.getMessage("ButtonStopLogging")); // NOI18N 134 stopLogButton.setVisible(true); 135 stopLogButton.setToolTipText(Bundle.getMessage("TooltipStopLogging")); // NOI18N 136 137 rawCheckBox.setText(Bundle.getMessage("ButtonShowRaw")); // NOI18N 138 rawCheckBox.setVisible(true); 139 rawCheckBox.setToolTipText(Bundle.getMessage("TooltipShowRaw")); // NOI18N 140 rawCheckBox.setSelected(p.getSimplePreferenceState(rawDataCheck)); 141 142 timeCheckBox.setText(Bundle.getMessage("ButtonShowTimestamps")); // NOI18N 143 timeCheckBox.setVisible(true); 144 timeCheckBox.setToolTipText(Bundle.getMessage("TooltipShowTimestamps")); // NOI18N 145 timeCheckBox.setSelected(p.getSimplePreferenceState(timeStampCheck)); 146 147 alwaysOnTopCheckBox.setText(Bundle.getMessage("ButtonWindowOnTop")); // NOI18N 148 alwaysOnTopCheckBox.setVisible(true); 149 alwaysOnTopCheckBox.setToolTipText(Bundle.getMessage("TooltipWindowOnTop")); // NOI18N 150 alwaysOnTopCheckBox.setSelected(p.getSimplePreferenceState(alwaysOnTopCheck)); 151 setAlwaysOnTop(alwaysOnTopCheckBox.isSelected()); 152 153 autoScrollCheckBox.setText(Bundle.getMessage("ButtonAutoScroll")); // NOI18N 154 autoScrollCheckBox.setVisible(true); 155 autoScrollCheckBox.setToolTipText(Bundle.getMessage("TooltipAutoScroll")); // NOI18N 156 autoScrollCheckBox.setSelected(!p.getSimplePreferenceState(autoScrollCheck)); 157 158 openFileChooserButton.setText(Bundle.getMessage("ButtonChooseLogFile")); // NOI18N 159 openFileChooserButton.setVisible(true); 160 openFileChooserButton.setToolTipText(Bundle.getMessage("TooltipChooseLogFile")); // NOI18N 161 162 setTitle(title()); 163 getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); 164 165 // add items to GUI 166 getContentPane().add(jScrollPane1); 167 168 JPanel paneA = new JPanel(); 169 paneA.setLayout(new BoxLayout(paneA, BoxLayout.Y_AXIS)); 170 171 JPanel pane1 = new JPanel(); 172 pane1.setLayout(new BoxLayout(pane1, BoxLayout.X_AXIS)); 173 pane1.add(clearButton); 174 pane1.add(freezeButton); 175 pane1.add(rawCheckBox); 176 pane1.add(timeCheckBox); 177 pane1.add(alwaysOnTopCheckBox); 178 paneA.add(pane1); 179 180 JPanel pane2 = new JPanel(); 181 pane2.setLayout(new BoxLayout(pane2, BoxLayout.X_AXIS)); 182 pane2.add(openFileChooserButton); 183 pane2.add(startLogButton); 184 pane2.add(stopLogButton); 185 paneA.add(pane2); 186 187 JPanel pane3 = new JPanel(); 188 pane3.setLayout(new BoxLayout(pane3, BoxLayout.X_AXIS)); 189 pane3.add(enterButton); 190 pane3.add(entryField); 191 paneA.add(pane3); 192 193 getContentPane().add(paneA); 194 195 // connect actions to buttons 196 clearButton.addActionListener(new ActionListener() { 197 @Override 198 public void actionPerformed(ActionEvent e) { 199 clearButtonActionPerformed(e); 200 } 201 }); 202 startLogButton.addActionListener(new ActionListener() { 203 @Override 204 public void actionPerformed(ActionEvent e) { 205 startLogButtonActionPerformed(e); 206 } 207 }); 208 stopLogButton.addActionListener(new ActionListener() { 209 @Override 210 public void actionPerformed(ActionEvent e) { 211 stopLogButtonActionPerformed(e); 212 } 213 }); 214 openFileChooserButton.addActionListener(new ActionListener() { 215 @Override 216 public void actionPerformed(ActionEvent e) { 217 openFileChooserButtonActionPerformed(e); 218 } 219 }); 220 221 enterButton.addActionListener(new ActionListener() { 222 @Override 223 public void actionPerformed(ActionEvent e) { 224 enterButtonActionPerformed(e); 225 } 226 }); 227 228 alwaysOnTopCheckBox.addActionListener(new ActionListener() { 229 @Override 230 public void actionPerformed(ActionEvent e) { 231 setAlwaysOnTop(alwaysOnTopCheckBox.isSelected()); 232 } 233 }); 234 235 autoScrollCheckBox.addActionListener(new ActionListener() { 236 @Override 237 public void actionPerformed(ActionEvent e) { 238 monTextPane.setAutoScroll(autoScrollCheckBox.isSelected()); 239 } 240 }); 241 242 // set file chooser to a default 243 logFileChooser.setSelectedFile(new File("monitorLog.txt")); // NOI18N 244 245 // connect to data source 246 init(); 247 248 // add help menu to window 249 setHelp(); 250 251 // prevent button areas from expanding 252 pack(); 253 paneA.setMaximumSize(paneA.getSize()); 254 pack(); 255 } 256 257 /** 258 * Define help menu for this window. 259 * <p> 260 * By default, provides a generic help page that covers general features. 261 * Specific implementations can override this to show their own help page if 262 * desired. 263 */ 264 protected void setHelp() { 265 addHelpMenu("package.jmri.jmrix.AbstractMonFrame", true); // NOI18N 266 } 267 268 public void nextLine(String line, String raw) { 269 // handle display of traffic 270 // line is the traffic in 'normal form', raw is the "raw form" 271 // Both should be one or more well-formed lines, e.g. end with \n 272 StringBuffer sb = new StringBuffer(120); 273 274 // display the timestamp if requested 275 if (timeCheckBox.isSelected()) { 276 sb.append(df.format(new Date())).append(": "); // NOI18N 277 } 278 279 // display the raw data if requested 280 if (rawCheckBox.isSelected()) { 281 sb.append('[').append(raw).append("] "); // NOI18N 282 } 283 284 // display decoded data 285 sb.append(line); 286 synchronized (self) { 287 linesBuffer.append(sb.toString()); 288 } 289 290 // if not frozen, display it in the Swing thread 291 if (!freezeButton.isSelected()) { 292 Runnable r = new Runnable() { 293 @Override 294 public void run() { 295 synchronized (self) { 296 monTextPane.append(linesBuffer.toString()); 297 linesBuffer.setLength(0); 298 } 299 } 300 }; 301 javax.swing.SwingUtilities.invokeLater(r); 302 } 303 304 // if requested, log to a file. 305 if (logStream != null) { 306 synchronized (logStream) { 307 String logLine = sb.toString(); 308 if (!newline.equals("\n")) { 309 // have to massage the line-ends 310 int i = 0; 311 int lim = sb.length(); 312 StringBuffer out = new StringBuffer(sb.length() + 10); // arbitrary guess at space 313 for (i = 0; i < lim; i++) { 314 if (sb.charAt(i) == '\n') { 315 out.append(newline); 316 } else { 317 out.append(sb.charAt(i)); 318 } 319 } 320 logLine = out.toString(); 321 } 322 logStream.print(logLine); 323 } 324 } 325 } 326 327 String newline = System.getProperty("line.separator"); // NOI18N 328 329 public synchronized void clearButtonActionPerformed(java.awt.event.ActionEvent e) { 330 // clear the monitoring history 331 synchronized (linesBuffer) { 332 linesBuffer.setLength(0); 333 monTextPane.setText(""); 334 } 335 } 336 337 public synchronized void startLogButtonActionPerformed(java.awt.event.ActionEvent e) { 338 // start logging by creating the stream 339 if (logStream == null) { // successive clicks don't restart the file 340 // start logging 341 try { 342 logStream = new PrintStream(new FileOutputStream(logFileChooser.getSelectedFile())); 343 } catch (java.io.FileNotFoundException ex) { 344 log.error("exception", ex); 345 } 346 } 347 } 348 349 public synchronized void stopLogButtonActionPerformed(java.awt.event.ActionEvent e) { 350 // stop logging by removing the stream 351 if (logStream != null) { 352 synchronized (logStream) { 353 logStream.flush(); 354 logStream.close(); 355 } 356 logStream = null; 357 } 358 } 359 360 public void openFileChooserButtonActionPerformed(java.awt.event.ActionEvent e) { 361 // start at current file, show dialog 362 int retVal = logFileChooser.showSaveDialog(this); 363 364 // handle selection or cancel 365 if (retVal == JFileChooser.APPROVE_OPTION) { 366 boolean loggingNow = (logStream != null); 367 stopLogButtonActionPerformed(e); // stop before changing file 368 //File file = logFileChooser.getSelectedFile(); 369 // if we were currently logging, start the new file 370 if (loggingNow) { 371 startLogButtonActionPerformed(e); 372 } 373 } 374 } 375 376 public void enterButtonActionPerformed(java.awt.event.ActionEvent e) { 377 nextLine(entryField.getText() + "\n", ""); // NOI18N 378 } 379 380 public synchronized String getFrameText() { 381 return linesBuffer.toString(); 382 } 383 384 /** 385 * Get access to the main text area. 386 * This is intended for use in e.g. scripting 387 * to extend the behaviour of the window. 388 * @return the text area. 389 */ 390 public final synchronized JTextArea getTextArea() { 391 return monTextPane; 392 } 393 394 volatile PrintStream logStream = null; 395 396 // to get a time string 397 DateFormat df = new SimpleDateFormat("HH:mm:ss.SSS"); 398 399 StringBuffer linesBuffer = new StringBuffer(); 400 static private int MAX_LINES = 500; 401 402 private static final Logger log = LoggerFactory.getLogger(AbstractMonFrame.class); 403 404}