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