001package jmri.jmrix; 002 003import java.awt.Component; 004import java.awt.Dimension; 005import java.awt.datatransfer.StringSelection; 006import java.awt.event.ActionEvent; 007import java.io.File; 008import java.io.FileOutputStream; 009import java.io.PrintStream; 010import java.text.DateFormat; 011import java.text.SimpleDateFormat; 012import java.util.Date; 013 014import javax.annotation.concurrent.GuardedBy; 015import javax.swing.BoxLayout; 016import javax.swing.JButton; 017import javax.swing.JCheckBox; 018import javax.swing.JFileChooser; 019import javax.swing.JLabel; 020import javax.swing.JPanel; 021import javax.swing.JScrollPane; 022import javax.swing.JTextArea; 023import javax.swing.JTextField; 024import javax.swing.JToggleButton; 025import javax.swing.SwingUtilities; 026import javax.swing.text.AbstractDocument; 027import javax.swing.text.AttributeSet; 028import javax.swing.text.BadLocationException; 029import javax.swing.text.DocumentFilter; 030 031import jmri.InstanceManager; 032import jmri.UserPreferencesManager; 033import jmri.util.FileUtil; 034import jmri.util.JmriJFrame; 035import jmri.util.swing.JmriJOptionPane; 036import jmri.util.swing.JmriPanel; 037import jmri.util.swing.TextAreaFIFO; 038import jmri.util.swing.WrapLayout; 039 040/** 041 * Abstract base class for JPanels displaying communications monitor 042 * information. 043 * 044 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2010 045 */ 046public abstract class AbstractMonPane extends JmriPanel { 047 048 /** 049 * {@inheritDoc} 050 */ 051 @Override 052 public abstract String getTitle(); // provide the title for the frame 053 054 /** 055 * Initialize the data source. 056 * <p> 057 * This is invoked at the end of the GUI initialization phase. Subclass 058 * implementations should connect to their data source here. 059 */ 060 protected abstract void init(); 061 062 /** 063 * {@inheritDoc} 064 */ 065 @Override 066 public void dispose() { 067 UserPreferencesManager pm = InstanceManager.getDefault(UserPreferencesManager.class); 068 pm.setSimplePreferenceState(timeStampCheck, timeCheckBox.isSelected()); 069 pm.setSimplePreferenceState(rawDataCheck, rawCheckBox.isSelected()); 070 pm.setSimplePreferenceState(alwaysOnTopCheck, alwaysOnTopCheckBox.isSelected()); 071 pm.setSimplePreferenceState(autoScrollCheck, !autoScrollCheckBox.isSelected()); 072 pm.setProperty(filterFieldCheck, filterFieldCheck, filterField.getText()); 073 monTextPane.dispose(); 074 super.dispose(); 075 } 076 // you'll also have to add the message(Foo) members to handle info to be logged. 077 // these should call nextLine(String line, String raw) with their updates 078 079 // member declarations 080 protected JButton copyToClipBoardButton = new JButton(); 081 protected JButton clearButton = new JButton(); 082 protected JToggleButton freezeButton = new JToggleButton(); 083 protected JScrollPane jScrollPane1 = new JScrollPane(); 084 protected TextAreaFIFO monTextPane = new TextAreaFIFO(MAX_LINES); 085 protected JToggleButton startLogButton = new JToggleButton(); 086 protected JButton stopLogButton = new JButton(); 087 protected JCheckBox rawCheckBox = new JCheckBox(); 088 protected JCheckBox timeCheckBox = new JCheckBox(); 089 protected JCheckBox alwaysOnTopCheckBox = new JCheckBox(); 090 protected JCheckBox autoScrollCheckBox = new JCheckBox(); 091 protected JTextField filterField = new JTextField(); 092 protected JLabel filterLabel = new JLabel(Bundle.getMessage("LabelFilterBytes"), JLabel.LEFT); // NOI18N 093 protected JButton openFileChooserButton = new JButton(); 094 protected JTextField entryField = new JTextField(); 095 protected JButton enterButton = new JButton(); 096 String rawDataCheck = this.getClass().getName() + ".RawData"; // NOI18N 097 String timeStampCheck = this.getClass().getName() + ".TimeStamp"; // NOI18N 098 String alwaysOnTopCheck = this.getClass().getName() + ".AlwaysOnTop"; // NOI18N 099 String autoScrollCheck = this.getClass().getName() + ".AutoScroll"; // NOI18N 100 String filterFieldCheck = this.getClass().getName() + ".FilterField"; // NOI18N 101 102 // to find and remember the log file 103 final javax.swing.JFileChooser logFileChooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath()); 104 105 public AbstractMonPane() { 106 super(); 107 } 108 109 /** 110 * By default, create just one place (one data pane) to put trace data. 111 */ 112 protected void createDataPanes() { 113 configureDataPane(monTextPane); 114 } 115 116 /** 117 * Do default configuration of a data pane. 118 * 119 * @param textPane a TextAreaFIFO into which the data pane will be placed 120 */ 121 protected void configureDataPane(TextAreaFIFO textPane) { 122 textPane.setVisible(true); 123 textPane.setToolTipText(Bundle.getMessage("TooltipMonTextPane")); // NOI18N 124 textPane.setEditable(false); 125 } 126 127 /** 128 * Provide initial preferred line length. Used to size the initial GUI. 129 * 130 * @return preferred initial number of columns 131 */ 132 protected int getInitialPreferredLineLength() { 133 return 80; 134 } 135 136 /** 137 * Provide initial number of lines to display Used to size the initial GUI. 138 * 139 * @return preferred initial number of rows 140 */ 141 protected int getInitialPreferredLineCount() { 142 return 10; 143 } 144 145 /** 146 * Put data pane(s) in the GUI. 147 */ 148 protected void addDataPanes() { 149 150 // fix a width for current character set 151 JTextField t = new JTextField(getInitialPreferredLineLength()); 152 int x = jScrollPane1.getPreferredSize().width + t.getPreferredSize().width; 153 int y = jScrollPane1.getPreferredSize().height + getInitialPreferredLineCount() * t.getPreferredSize().height; 154 155 jScrollPane1.getViewport().add(monTextPane); 156 jScrollPane1.setPreferredSize(new Dimension(x, y)); 157 jScrollPane1.setVisible(true); 158 159 // add in a JPanel that stays sized as the window changes size 160 JPanel p = new JPanel(); 161 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); 162 p.add(jScrollPane1); 163 add(p); 164 } 165 166 /** 167 * {@inheritDoc} 168 */ 169 @Override 170 public void initComponents() { 171 UserPreferencesManager pm = InstanceManager.getDefault(UserPreferencesManager.class); 172 173 // the following code sets the frame's initial state 174 copyToClipBoardButton.setText(java.util.ResourceBundle.getBundle("apps.AppsBundle").getString("ButtonCopyClip")); 175 copyToClipBoardButton.setVisible(true); 176 177 clearButton.setText(Bundle.getMessage("ButtonClearScreen")); // NOI18N 178 clearButton.setVisible(true); 179 clearButton.setToolTipText(Bundle.getMessage("TooltipClearMonHistory")); // NOI18N 180 181 freezeButton.setText(Bundle.getMessage("ButtonFreezeScreen")); // NOI18N 182 freezeButton.setVisible(true); 183 freezeButton.setToolTipText(Bundle.getMessage("TooltipStopScroll")); // NOI18N 184 185 enterButton.setText(Bundle.getMessage("ButtonAddMessage")); // NOI18N 186 enterButton.setVisible(true); 187 enterButton.setToolTipText(Bundle.getMessage("TooltipAddMessage")); // NOI18N 188 189 createDataPanes(); 190 191 entryField.setToolTipText(Bundle.getMessage("TooltipEntryPane")); // NOI18N 192 // cap vertical size to avoid over-growth 193 Dimension currentPreferredSize = entryField.getPreferredSize(); 194 Dimension currentMaximumSize = entryField.getMaximumSize(); 195 currentMaximumSize.height = currentPreferredSize.height; 196 entryField.setMaximumSize(currentMaximumSize); 197 198 //setup filterField 199 filterField.setToolTipText(Bundle.getMessage("TooltipFilter")); // NOI18N 200 filterField.setMaximumSize(currentMaximumSize); 201 try { 202 filterField.setText(pm.getProperty(filterFieldCheck, filterFieldCheck).toString()); //restore prev values 203 } catch (NullPointerException e1) { 204 // leave blank if previous value not retrieved 205 } 206 //automatically uppercase input in filterField, and only accept spaces and valid hex characters 207 ((AbstractDocument) filterField.getDocument()).setDocumentFilter(new DocumentFilter() { 208 final private static String PATTERN = "[0-9a-fA-F ]*+"; // typing inserts individual characters 209 210 @Override 211 public void insertString(DocumentFilter.FilterBypass fb, int offset, String text, 212 AttributeSet attrs) throws BadLocationException { 213 if (text.matches(PATTERN)) { // NOI18N 214 fb.insertString(offset, text.toUpperCase(), attrs); 215 } else { 216 fb.insertString(offset, "", attrs); 217 } 218 } 219 220 @Override 221 public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, 222 AttributeSet attrs) throws BadLocationException { 223 if (text.matches(PATTERN)) { // NOI18N 224 fb.replace(offset, length, text.toUpperCase(), attrs); 225 } else { 226 fb.replace(offset, length, "", attrs); 227 } 228 } 229 }); 230 231 startLogButton.setText(Bundle.getMessage("ButtonStartLogging")); // NOI18N 232 startLogButton.setVisible(true); 233 startLogButton.setToolTipText(Bundle.getMessage("TooltipStartLogging")); // NOI18N 234 235 stopLogButton.setText(Bundle.getMessage("ButtonStopLogging")); // NOI18N 236 stopLogButton.setVisible(true); 237 stopLogButton.setToolTipText(Bundle.getMessage("TooltipStopLogging")); // NOI18N 238 239 rawCheckBox.setText(Bundle.getMessage("ButtonShowRaw")); // NOI18N 240 rawCheckBox.setVisible(true); 241 rawCheckBox.setToolTipText(Bundle.getMessage("TooltipShowRaw")); // NOI18N 242 rawCheckBox.setSelected(pm.getSimplePreferenceState(rawDataCheck)); 243 244 timeCheckBox.setText(Bundle.getMessage("ButtonShowTimestamps")); // NOI18N 245 timeCheckBox.setVisible(true); 246 timeCheckBox.setToolTipText(Bundle.getMessage("TooltipShowTimestamps")); // NOI18N 247 timeCheckBox.setSelected(pm.getSimplePreferenceState(timeStampCheck)); 248 249 alwaysOnTopCheckBox.setText(Bundle.getMessage("ButtonWindowOnTop")); // NOI18N 250 alwaysOnTopCheckBox.setVisible(true); 251 alwaysOnTopCheckBox.setToolTipText(Bundle.getMessage("TooltipWindowOnTop")); // NOI18N 252 alwaysOnTopCheckBox.setSelected(pm.getSimplePreferenceState(alwaysOnTopCheck)); 253 Component ancestor = getTopLevelAncestor(); 254 if (ancestor instanceof JmriJFrame) { 255 ((JmriJFrame) ancestor).setAlwaysOnTop(alwaysOnTopCheckBox.isSelected()); 256 } else { 257 // this pane isn't yet part of a frame, 258 // which can be normal, but 259 if (alwaysOnTopCheckBox.isSelected()) { 260 // in this case we want to access the enclosing frame to setAlwaysOnTop. So defer for a bit.... 261 log.debug("Cannot set Always On Top from preferences due to no Top Level Ancestor"); 262 timerCount = 0; 263 timer = new javax.swing.Timer(20, (java.awt.event.ActionEvent evt) -> { 264 if ((getTopLevelAncestor() != null) && (timerCount > 3) && (getTopLevelAncestor() instanceof JmriJFrame)) { 265 timer.stop(); 266 ((JmriJFrame) getTopLevelAncestor()).setAlwaysOnTop(alwaysOnTopCheckBox.isSelected()); 267 log.debug("set Always On Top"); 268 } else { 269 log.debug("Have to repeat attempt to set Always on Top"); 270 timerCount++; 271 if (timerCount > 50) { 272 log.warn("Took too long to \"Set Always on Top\", failed"); 273 timer.stop(); 274 } 275 } 276 }); 277 timer.start(); 278 } 279 } 280 281 autoScrollCheckBox.setText(Bundle.getMessage("ButtonAutoScroll")); // NOI18N 282 autoScrollCheckBox.setVisible(true); 283 autoScrollCheckBox.setToolTipText(Bundle.getMessage("TooltipAutoScroll")); // NOI18N 284 autoScrollCheckBox.setSelected(!pm.getSimplePreferenceState(autoScrollCheck)); 285 monTextPane.setAutoScroll(!pm.getSimplePreferenceState(autoScrollCheck)); 286 287 openFileChooserButton.setText(Bundle.getMessage("ButtonChooseLogFile")); // NOI18N 288 openFileChooserButton.setVisible(true); 289 openFileChooserButton.setToolTipText(Bundle.getMessage("TooltipChooseLogFile")); // NOI18N 290 291 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 292 293 // add items to GUI 294 addDataPanes(); 295 296 JPanel paneA = new JPanel(); 297 paneA.setLayout(new BoxLayout(paneA, BoxLayout.Y_AXIS)); 298 299 JPanel pane1 = new JPanel(){ 300 @Override 301 public Dimension getPreferredSize() { 302 Dimension min = super.getMinimumSize(); 303 Dimension max = super.getMaximumSize(); 304 return new Dimension(max.width, min.height); 305 } 306 @Override 307 public Dimension getMaximumSize() { 308 return getPreferredSize(); 309 } 310 }; 311 pane1.setLayout(new WrapLayout()); 312 pane1.add(copyToClipBoardButton); 313 pane1.add(clearButton); 314 pane1.add(freezeButton); 315 pane1.add(rawCheckBox); 316 pane1.add(timeCheckBox); 317 pane1.add(alwaysOnTopCheckBox); 318 pane1.add(autoScrollCheckBox); 319 paneA.add(pane1); 320 addCustomControlPanes(paneA); 321 322 JPanel pane2 = new JPanel(); 323 pane2.setLayout(new BoxLayout(pane2, BoxLayout.X_AXIS)); 324 pane2.add(filterLabel); 325 filterLabel.setLabelFor(filterField); 326 pane2.add(filterField); 327 pane2.add(openFileChooserButton); 328 pane2.add(startLogButton); 329 pane2.add(stopLogButton); 330 paneA.add(pane2); 331 332 JPanel pane3 = new JPanel(); 333 pane3.setLayout(new BoxLayout(pane3, BoxLayout.X_AXIS)); 334 pane3.add(enterButton); 335 pane3.add(entryField); 336 paneA.add(pane3); 337 338 add(paneA); 339 340 // connect actions to buttons 341 copyToClipBoardButton.addActionListener(this::copyToClipBoardButtonActionPerformed); 342 343 clearButton.addActionListener((java.awt.event.ActionEvent e) -> { 344 clearButtonActionPerformed(e); 345 }); 346 startLogButton.addActionListener((java.awt.event.ActionEvent e) -> { 347 startLogButtonActionPerformed(e); 348 }); 349 stopLogButton.addActionListener((java.awt.event.ActionEvent e) -> { 350 stopLogButtonActionPerformed(e); 351 }); 352 openFileChooserButton.addActionListener((java.awt.event.ActionEvent e) -> { 353 openFileChooserButtonActionPerformed(e); 354 }); 355 356 enterButton.addActionListener((java.awt.event.ActionEvent e) -> { 357 enterButtonActionPerformed(e); 358 }); 359 360 alwaysOnTopCheckBox.addActionListener((java.awt.event.ActionEvent e) -> { 361 if ((getTopLevelAncestor() != null) && (getTopLevelAncestor() instanceof JmriJFrame)) { 362 ((JmriJFrame) getTopLevelAncestor()).setAlwaysOnTop(alwaysOnTopCheckBox.isSelected()); 363 } 364 }); 365 366 autoScrollCheckBox.addActionListener((ActionEvent e) -> { 367 monTextPane.setAutoScroll(autoScrollCheckBox.isSelected()); 368 }); 369 370 // set file chooser to a default 371 logFileChooser.setSelectedFile(new File("monitorLog.txt")); 372 373 // connect to data source 374 init(); 375 376 } 377 378 /** 379 * Expand the display with additional options specific to the hardware. 380 * @param parent a Panel (with vertical BoxLayout); overrides should add a new Panel with horizontal BoxLayout to hold the additional options. 381 */ 382 protected void addCustomControlPanes(JPanel parent) { 383 } 384 385 private int timerCount = 0; 386 private javax.swing.Timer timer; 387 388 /** 389 * Set the display window to fixed width font, so that e.g. columns line up. 390 */ 391 public void setFixedWidthFont() { 392 monTextPane.setFont(new java.awt.Font("Monospaced", java.awt.Font.PLAIN, monTextPane.getFont().getSize())); 393 } 394 395 /** 396 * {@inheritDoc} 397 */ 398 @Override 399 public String getHelpTarget() { 400 return "package.jmri.jmrix.AbstractMonFrame"; // NOI18N 401 } 402 403 /** 404 * Log an Message derived message. 405 * 406 * @param message message object to log. 407 */ 408 public void logMessage(Message message) { 409 logMessage("", "", message); 410 } 411 412 /** 413 * Log an Message derived message. 414 * 415 * @param messagePrefix text to prefix the message with. 416 * @param message message object to log. 417 */ 418 public void logMessage(String messagePrefix, Message message) { 419 logMessage(messagePrefix, "", message); 420 } 421 422 /** 423 * Log an Message derived message with a prefixed label. 424 * 425 * @param messagePrefix text to prefix the message with. 426 * @param rawPrefix label to add to the start of the message. 427 * @param message message object to log. 428 */ 429 public void logMessage(String messagePrefix, String rawPrefix, Message message){ 430 // display the raw data if requested 431 StringBuilder raw = new StringBuilder(rawPrefix); 432 if (rawCheckBox.isSelected()) { 433 raw.append(message.toString()); 434 } 435 436 // display the decoded data 437 String text = message.toMonitorString(); 438 nextLine(messagePrefix + " " + text + "\n", raw.toString()); 439 } 440 441 442 public void nextLine(String line, String raw) { 443 nextLineWithTime(new Date(), line, raw); 444 } 445 446 /** 447 * Handle display of traffic. 448 * 449 * @param timestamp timestamp to be pre-pended to the output line (if 450 * timestamping is enabled) 451 * @param line The traffic in normal parsed form, ending with \n 452 * @param raw The traffic in raw form, ending with \n 453 */ 454 public void nextLineWithTime(Date timestamp, String line, String raw) { 455 456 StringBuilder sb = new StringBuilder(120); 457 458 // display the timestamp if requested 459 if (timeCheckBox.isSelected()) { 460 sb.append(df.format(timestamp)).append(": "); 461 } 462 463 // display the raw data if available and requested 464 if (raw != null && rawCheckBox.isSelected()) { 465 sb.append('[').append(raw).append("] "); // NOI18N 466 } 467 468 // display parsed data 469 sb.append(line); 470 synchronized (this) { 471 linesBuffer.append(sb.toString()); 472 } 473 474 // if requested, log to a file. 475 synchronized (this) { 476 if (logStream != null) { 477 String logLine = sb.toString(); 478 if (!newline.equals("\n")) { // NOI18N 479 // have to massage the line-ends 480 int lim = sb.length(); 481 StringBuilder out = new StringBuilder(sb.length() + 10); // arbitrary guess at space 482 for (int i = 0; i < lim; i++) { 483 if (sb.charAt(i) == '\n') { // NOI18N 484 out.append(newline); 485 } else { 486 out.append(sb.charAt(i)); 487 } 488 } 489 logLine = out.toString(); 490 } 491 logStream.print(logLine); 492 } 493 } 494 495 // if frozen, exit without adding to the Swing thread 496 if (freezeButton.isSelected()) { 497 return; 498 } 499 500 // if this message is filtered out, end 501 if (isFiltered(raw)) { 502 return; 503 } 504 505 SwingUtilities.invokeLater(() -> { 506 synchronized (AbstractMonPane.this) { 507 monTextPane.append(linesBuffer.toString()); 508 linesBuffer.setLength(0); 509 } 510 }); 511 } 512 513 /** 514 * Default filtering implementation, more of an example than anything else, 515 * not clear it really works for any system. Override this in 516 * system-specific subclasses to do something useful. 517 * 518 * @param raw A string containing the raw message hex information, in ASCII 519 * encoding, with some "header" information pre-pended. 520 * @return True if the opcode in the raw message matches one of the "filter" 521 * opcodes. False if the opcode does not match any of the "filter" 522 * opcodes. 523 */ 524 protected boolean isFiltered(String raw) { 525 String checkRaw = getOpCodeForFilter(raw); 526 //don't bother to check filter if no raw value passed 527 if (raw != null) { 528 // if first bytes are in the skip list, exit without adding to the Swing thread 529 String[] filters = filterField.getText().toUpperCase().split(" "); 530 531 for (String s : filters) { 532 if (s.equals(checkRaw)) { 533 synchronized (this) { 534 linesBuffer.setLength(0); 535 } 536 return true; 537 } 538 } 539 } 540 return false; 541 } 542 543 /** 544 * Get hex opcode for filtering. 545 * <p> 546 * Reports the "opcode" byte from the string containing the ASCII string 547 * representation of the message. Assumes that there is a generic header on 548 * string, like "Tx - ", and ignores it. 549 * 550 * @param raw a String containing the generic raw hex information, with 551 * pre-pended header. 552 * 553 * @return a two character String containing only the hex representation of 554 * the opcode from the raw message. 555 */ 556 protected String getOpCodeForFilter(String raw) { 557 // note: Generic raw is formatted like "Tx - BB 01 00 45", so extract the correct bytes from it (BB) for comparison 558 if (raw != null && raw.length() >= 7) { 559 return raw.substring(5, 7); 560 } else { 561 return null; 562 } 563 } 564 565 private static final String newline = System.getProperty("line.separator"); // NOI18N 566 567 public synchronized void clearButtonActionPerformed(java.awt.event.ActionEvent e) { 568 // clear the monitoring history 569 linesBuffer.setLength(0); 570 monTextPane.setText(""); 571 } 572 573 public synchronized void copyToClipBoardButtonActionPerformed(java.awt.event.ActionEvent e) { 574 StringSelection text = new StringSelection(monTextPane.getText()); 575 java.awt.Toolkit.getDefaultToolkit().getSystemClipboard().setContents(text, text); 576 } 577 578 public String getFilePathAndName() { 579 String returnString = ""; 580 java.nio.file.Path p = logFileChooser.getSelectedFile().toPath(); 581 if (p.getParent() == null) { 582 // This case is a file path with a "parent" of "null" 583 // 584 // Should instead use the profile directory, as "null" can default to 585 // the JMRI program directory, which might not be user-writable. 586 java.nio.file.Path fileName = p.getFileName(); 587 if (fileName != null) { // getUserFilesPath() never null 588 returnString = FileUtil.getUserFilesPath() + fileName.toString(); 589 } else { 590 log.error("User Files File Path not valid"); 591 } 592 log.warn("File selection dialog box did not provide a path to the specified file. Log will be saved to {}", 593 returnString); 594 } else { 595 returnString = p.toString(); 596 } 597 return returnString; 598 } 599 600 public synchronized void startLogButtonActionPerformed(java.awt.event.ActionEvent e) { 601 // start logging by creating the stream 602 if (logStream == null) { // successive clicks won't restart the file once running 603 // start logging 604 String filePathAndName = getFilePathAndName(); 605 log.warn("startLogButtonActionPerformed: getSelectedFile() returns {} {}", 606 logFileChooser.getSelectedFile().getPath(), logFileChooser.getSelectedFile().getName()); 607 log.warn("startLogButtonActionPerformed: is attempting to use returned file path and file name {}", 608 filePathAndName); 609 File logFile = new File(filePathAndName); 610 try { 611 logStream = new PrintStream(new FileOutputStream(logFile)); 612 } catch (java.io.FileNotFoundException ex) { 613 stopLogButtonActionPerformed(null); 614 log.error("startLogButtonActionPerformed: FileOutputStream cannot open the file '{}'. Exception: {}", logFileChooser.getSelectedFile().getName(), ex.getMessage()); 615 JmriJOptionPane.showMessageDialog(this, 616 (Bundle.getMessage("ErrorCannotOpenFileForWriting", 617 logFileChooser.getSelectedFile().getName(), 618 Bundle.getMessage("ErrorPossibleCauseCannotOpenForWrite"))), 619 Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE); 620 } 621 } else { 622 startLogButton.setSelected(true); // keep toggle on 623 } 624 } 625 626 public synchronized void stopLogButtonActionPerformed(java.awt.event.ActionEvent e) { 627 // stop logging by removing the stream 628 if (logStream != null) { 629 logStream.flush(); 630 logStream.close(); 631 logStream = null; 632 } 633 startLogButton.setSelected(false); 634 } 635 636 public void openFileChooserButtonActionPerformed(java.awt.event.ActionEvent e) { 637 // start at current file, show dialog 638 int retVal = logFileChooser.showSaveDialog(this); 639 640 // handle selection or cancel 641 if (retVal == JFileChooser.APPROVE_OPTION) { 642 synchronized (this) { 643 boolean loggingNow = (logStream != null); 644 stopLogButtonActionPerformed(e); // stop before changing file 645 //File file = logFileChooser.getSelectedFile(); 646 // if we were currently logging, start the new file 647 if (loggingNow) { 648 startLogButtonActionPerformed(e); 649 } 650 } 651 } 652 } 653 654 public void enterButtonActionPerformed(java.awt.event.ActionEvent e) { 655 nextLine(entryField.getText() + "\n", null); // NOI18N 656 } 657 658 public synchronized String getFrameText() { 659 return monTextPane.getText(); 660 } 661 662 /** 663 * Get access to the main text area. This is intended for use in e.g. 664 * scripting to extend the behavior of the window. 665 * 666 * @return the main text area 667 */ 668 public final synchronized JTextArea getTextArea() { 669 return monTextPane; 670 } 671 672 public synchronized String getFilterText() { 673 return filterField.getText(); 674 } 675 676 public synchronized void setFilterText(String text) { 677 filterField.setText(text); 678 } 679 680 @GuardedBy("this") 681 private volatile PrintStream logStream = null; 682 683 // to get a time string 684 private final DateFormat df = new SimpleDateFormat("HH:mm:ss.SSS"); 685 686 @GuardedBy("this") 687 protected StringBuffer linesBuffer = new StringBuffer(); 688 private static final int MAX_LINES = 500; 689 690 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractMonPane.class); 691 692}