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