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