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