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