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