001package jmri.jmrix.can.cbus.swing.console;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.event.ActionEvent;
006import java.util.concurrent.ConcurrentLinkedDeque;
007
008import javax.swing.*;
009import javax.swing.text.BadLocationException;
010import javax.swing.text.DefaultHighlighter;
011import javax.swing.text.Highlighter;
012
013import jmri.jmrix.can.CanSystemConnectionMemo;
014import jmri.jmrix.can.TrafficController;
015import jmri.jmrix.can.cbus.CbusConfigurationManager;
016import jmri.jmrix.can.cbus.eventtable.CbusEventTableDataModel;
017import jmri.jmrix.can.cbus.swing.CbusEventHighlightFrame;
018import jmri.jmrix.can.cbus.swing.CbusSendEventPane;
019import jmri.util.ThreadingUtil;
020import jmri.util.swing.TextAreaFIFO;
021
022/**
023 * Frame for CBUS Console
024 *
025 * @author Andrew Crosland Copyright (C) 2008
026 * @author Steve Young Copyright (C) 2018
027 */
028public class CbusConsolePane extends jmri.jmrix.can.swing.CanPanel {
029
030    static int console_instance_num;
031    static final private int MAX_LINES = 5000;
032
033    private final ConcurrentLinkedDeque<CbusConsoleLogEntry> logBuffer;
034
035    private JToggleButton freezeButton;
036
037    public TextAreaFIFO monTextPaneCan;
038    public TextAreaFIFO monTextPaneCbus;
039    private Highlighter cbusHighlighter;
040    private Highlighter canHighlighter;
041
042    protected final CbusConsoleStatsPane statsPane;
043    protected final CbusConsolePacketPane packetPane;
044    protected final CbusSendEventPane sendPane;
045    protected CbusConsoleDecodeOptionsPane decodePane;
046    protected final CbusConsoleLoggingPane logPane;
047    public final CbusConsoleDisplayOptionsPane displayPane;
048
049    // members for handling the CBUS interface
050    protected TrafficController tc;
051
052    public CbusConsolePane() {
053        super();
054        incrementInstance();
055        logBuffer = new ConcurrentLinkedDeque<>();
056        statsPane = new CbusConsoleStatsPane(this);
057        packetPane = new CbusConsolePacketPane(this);
058        sendPane = new CbusSendEventPane(this);
059        logPane = new CbusConsoleLoggingPane(this);
060        displayPane = new CbusConsoleDisplayOptionsPane(this);
061
062    }
063
064    public static int getConsoleInstanceNum() {
065        return console_instance_num;
066    }
067
068    public static void incrementInstance() {
069        console_instance_num++;
070    }
071
072    /**
073     * {@inheritDoc}
074     */
075    @Override
076    public String getTitle() {
077        if (memo != null) {
078            StringBuilder title = new StringBuilder(20);
079            title.append(memo.getUserName()).append(" ");
080            title.append(Bundle.getMessage("CbusConsoleTitle"));
081            if (getConsoleInstanceNum() > 1) {
082                title.append(" ").append( getConsoleInstanceNum() );
083            }
084            return title.toString();
085        }
086        return Bundle.getMessage("CbusConsoleTitle");
087    }
088
089    /**
090     * {@inheritDoc}
091     */
092    @Override
093    public String getHelpTarget() {
094        return "package.jmri.jmrix.can.cbus.swing.console.CbusConsoleFrame";
095    }
096
097    /**
098     * {@inheritDoc}
099     */
100    @Override
101    public void dispose() {
102        if (decodePane!=null) {
103            decodePane.dispose();
104        }
105        displayPane.dispose();
106        statsPane.dispose();
107        super.dispose();
108    }
109
110    /**
111     * {@inheritDoc}
112     */
113    @Override
114    public void initComponents(CanSystemConnectionMemo memo) {
115        initComponents( memo, true);
116    }
117
118    /**
119     * Constructor For testing purposes, not for general use.
120     * @param memo System Connection
121     * @param launchEvTable true to launch a CBUS Event Table Model, else false.
122     */
123    public void initComponents(CanSystemConnectionMemo memo, boolean launchEvTable) {
124        super.initComponents(memo);
125        tc = memo.getTrafficController();
126        decodePane = new CbusConsoleDecodeOptionsPane(this);
127        if (launchEvTable){
128            memo.get(CbusConfigurationManager.class).provide(CbusEventTableDataModel.class);
129        }
130        init();
131    }
132
133    public void init() {
134
135        initTextAreas();
136
137        // Sub-pane to hold buttons
138        JPanel paneA = new JPanel();
139        paneA.setLayout(new BoxLayout(paneA, BoxLayout.Y_AXIS));
140        paneA.add(getClearFreezeButtonPane());
141        paneA.add(decodePane);
142
143        JPanel historyPane = new JPanel();
144        historyPane.setLayout(new BorderLayout());
145        historyPane.setBorder(BorderFactory.createTitledBorder(
146                BorderFactory.createEtchedBorder(), Bundle.getMessage("PacketHistoryTitle")));
147
148        historyPane.add(getSplitPane(), BorderLayout.CENTER);
149        historyPane.add(paneA, BorderLayout.SOUTH);
150
151        setLayout(new BorderLayout());
152        add(displayPane, BorderLayout.NORTH);
153        add(historyPane, BorderLayout.CENTER);
154        add(getAllBottomPanes(), BorderLayout.SOUTH);
155        displayPane.matchVisisbleToCheckBoxes(null);
156
157    }
158
159    private void initTextAreas() {
160
161        monTextPaneCan = new TextAreaFIFO(MAX_LINES);
162        monTextPaneCan.setVisible(true);
163        monTextPaneCan.setToolTipText(Bundle.getMessage("TooltipMonTextPaneCan"));
164        monTextPaneCan.setEditable(false);
165        monTextPaneCan.setRows(5);
166        monTextPaneCan.setColumns(5);
167
168        monTextPaneCbus = new TextAreaFIFO(MAX_LINES);
169        monTextPaneCbus.setVisible(true);
170        monTextPaneCbus.setToolTipText(Bundle.getMessage("TooltipMonTextPaneCbus"));
171        monTextPaneCbus.setEditable(false);
172        monTextPaneCbus.setRows(5);
173        monTextPaneCbus.setColumns(20);
174
175        cbusHighlighter = monTextPaneCbus.getHighlighter();
176        canHighlighter = monTextPaneCan.getHighlighter();
177
178    }
179
180    private JSplitPane getSplitPane(){
181
182        JScrollPane jScrollPane1Can = new JScrollPane();
183        jScrollPane1Can.getViewport().add(monTextPaneCan);
184        jScrollPane1Can.setVisible(true);
185        jScrollPane1Can.setBorder(BorderFactory.createTitledBorder(
186            BorderFactory.createEtchedBorder(), Bundle.getMessage("CanFrameTitle")));
187
188        JScrollPane jScrollPane1Cbus = new JScrollPane();
189        jScrollPane1Cbus.setBorder(BorderFactory.createTitledBorder(
190            BorderFactory.createEtchedBorder(), Bundle.getMessage("CbusMessageTitle")));
191        jScrollPane1Cbus.getViewport().add(monTextPaneCbus);
192        jScrollPane1Cbus.setVisible(true);
193
194        jScrollPane1Can.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
195        jScrollPane1Cbus.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
196        jScrollPane1Can.setVerticalScrollBar(jScrollPane1Cbus.getVerticalScrollBar());
197
198        // scroll panels to be side-by-side
199        JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
200            jScrollPane1Can, jScrollPane1Cbus);
201        split.setResizeWeight(0.3);
202        split.setContinuousLayout(true);
203
204        return split;
205    }
206
207    private JPanel getClearFreezeButtonPane() {
208
209        JPanel messageButtonOptionpane = new JPanel();
210
211        JButton clearButton = new JButton();
212        freezeButton = new JToggleButton();
213
214        clearButton.setText(Bundle.getMessage("ButtonClearScreen"));
215        clearButton.setToolTipText(Bundle.getMessage("ButtonClearLogTip"));
216
217        freezeButton.setText(Bundle.getMessage("ButtonFreezeScreen"));
218        freezeButton.setToolTipText(Bundle.getMessage("TooltipStopScroll"));
219
220        messageButtonOptionpane.setLayout(new BoxLayout(messageButtonOptionpane, BoxLayout.X_AXIS));
221        messageButtonOptionpane.add(clearButton);
222        messageButtonOptionpane.add(freezeButton);
223
224        clearButton.addActionListener(this::clearButtonActionPerformed);
225        freezeButton.addActionListener(this::freezeButtonActionPerformed);
226        return messageButtonOptionpane;
227
228    }
229
230    private JPanel getAllBottomPanes() {
231
232        JPanel southPane = new JPanel();
233        southPane.setLayout(new BoxLayout(southPane, BoxLayout.Y_AXIS));
234
235        logPane.setVisible(false);
236        statsPane.setVisible(false);
237        packetPane.setVisible(false);
238        sendPane.setVisible(false);
239
240        southPane.add(logPane);
241        southPane.add(statsPane);
242        southPane.add(packetPane);
243        southPane.add(sendPane);
244
245        return southPane;
246
247    }
248
249    /**
250     * Handle display of traffic.
251     * @param line        string the traffic in 'normal form',
252     * @param decoded     string the decoded, protocol specific, form.
253     * Both should contain the same number of well-formed lines, e.g. end with \n
254     * @param highlight   int
255     */
256    public void nextLine(String line, String decoded, int highlight) {
257
258        logBuffer.add( new CbusConsoleLogEntry(line,decoded,highlight));
259
260        // if not frozen, display it in the Swing thread
261        if (!freezeButton.isSelected()) {
262            ThreadingUtil.runOnGUIEventually( ()->{
263                processLogBuffer();
264            });
265        }
266
267        // if requested, log to a file.
268        logPane.sendLogToFile( decoded );
269
270    }
271
272    private void processLogBuffer() {
273        while (!logBuffer.isEmpty()){
274            CbusConsoleLogEntry next = logBuffer.removeFirst();
275
276            final int start = monTextPaneCbus.getText().length();
277            final int startc= monTextPaneCan.getText().length();
278
279            monTextPaneCan.append(next.getFrameText());
280            monTextPaneCbus.append(next.getDecodedText());
281
282            if (next.getHighlighter() > -1) {
283                try {
284                    CbusHighlightPainter cbusHighlightPainter = new CbusHighlightPainter(
285                        CbusEventHighlightFrame.highlightColors[next.getHighlighter()]);
286                    // log.debug("Add highlight start: " + start + " end: " + end);
287                    cbusHighlighter.addHighlight(start, monTextPaneCbus.getText().length() - 1, cbusHighlightPainter);
288                    canHighlighter.addHighlight(startc, monTextPaneCan.getText().length() - 1, cbusHighlightPainter);
289                } catch (BadLocationException e) {} // do nothing
290            }
291        }
292    }
293
294    // clear the monitoring history
295    private void clearButtonActionPerformed(ActionEvent e) {
296        logBuffer.clear();
297        monTextPaneCan.setText("");
298        monTextPaneCbus.setText("");
299    }
300
301    private void freezeButtonActionPerformed(ActionEvent e) {
302        if (freezeButton.isSelected()) {
303            freezeButton.setForeground(Color.red);
304        } else {
305            freezeButton.setForeground(new JTextField().getForeground()); // reset to default
306            nextLine("","",-1); // poke with zero content to refresh screen
307        }
308    }
309
310    // A private subclass of the default highlight painter
311    private class CbusHighlightPainter extends DefaultHighlighter.DefaultHighlightPainter {
312        protected CbusHighlightPainter(Color color) {
313            super(color);
314        }
315    }
316
317    /**
318     * Nested class to create one of these using old-style defaults.
319     */
320    static public class Default extends jmri.jmrix.can.swing.CanNamedPaneAction {
321
322        public Default() {
323            super(Bundle.getMessage("CbusConsoleTitle"),
324                    new jmri.util.swing.sdi.JmriJFrameInterface(),
325                    CbusConsolePane.class.getName(),
326                    jmri.InstanceManager.getDefault(CanSystemConnectionMemo.class));
327        }
328    }
329
330    // private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusConsolePane.class);
331}