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