001package jmri.jmrix.dccpp.swing.mon;
002
003import jmri.jmrix.dccpp.DCCppListener;
004import jmri.jmrix.dccpp.DCCppMessage;
005import jmri.jmrix.dccpp.DCCppReply;
006import jmri.jmrix.dccpp.DCCppSystemConnectionMemo;
007import jmri.jmrix.dccpp.DCCppTrafficController;
008import jmri.jmrix.dccpp.serial.SerialDCCppPacketizer;
009
010import java.awt.BorderLayout;
011import java.awt.Dimension;
012import java.awt.FlowLayout;
013import java.awt.event.ActionEvent;
014import java.awt.event.ComponentAdapter;
015import java.awt.event.ComponentEvent;
016
017import javax.swing.*;
018
019import jmri.util.swing.TextAreaFIFO;
020import jmri.util.swing.WrapLayout;
021
022/**
023 * Frame displaying (and logging) DCCpp command messages.
024 *
025 * @author Bob Jacobsen Copyright (C) 2001
026 * @author Chuck Catania  Copyright (C) 2014, 2016, 2017
027 * @author mstevetodd  Copyright (C) 2021
028 */
029public class DCCppMonFrame extends jmri.jmrix.AbstractMonFrame implements DCCppListener {
030
031    // member declarations
032    final java.util.ResourceBundle rb = java.util.ResourceBundle.getBundle("jmri.jmrix.dccpp.swing.DCCppSwingBundle"); // NOI18N
033
034    private DCCppTrafficController tc = null;
035    private DCCppSystemConnectionMemo _memo = null;
036
037    private SerialDCCppPacketizer serialDCCppTC = null;
038
039    private final JPanel serialPane = new JPanel();
040    private final JLabel queuedEntriesLabel = new JLabel("", SwingConstants.LEFT); // NOI18N
041    private final JToggleButton pauseRefreshButton = new JToggleButton();
042    private final JButton clearRefreshQueueButton = new JButton();
043    private final JCheckBox displayTranslatedCheckBox = new JCheckBox(Bundle.getMessage("ButtonShowTranslation"));
044
045    private final String doNotDisplayTranslatedCheck = this.getClass().getName() + ".DoNotDisplayTranslated"; // NOI18N
046
047    public DCCppMonFrame(DCCppSystemConnectionMemo memo) {
048        super();
049        _memo = memo;
050        // Match DCC-EX's native <...> command syntax for the raw display.
051        rawOpenBracket = "<";
052        rawCloseBracket = ">";
053        // DCC-EX startup enumerates all turnouts/outputs one at a time, which
054        // easily exceeds the default 500-line limit on large layouts.
055        monTextPane = new TextAreaFIFO(2000);
056    }
057
058    @Override
059    protected String title() {
060        return rb.getString("DCCppMonFrameTitle")+" (" + _memo.getSystemPrefix() + ")";  // NOI18N
061    }
062
063    @Override
064    protected void init() {
065
066        // connect to TrafficController
067        tc = _memo.getDCCppTrafficController();
068        tc.addDCCppListener(~0, this);
069        
070        if ((tc instanceof SerialDCCppPacketizer) && tc.getCommandStation().isFunctionRefreshRequired()) {
071            serialDCCppTC = (SerialDCCppPacketizer) tc;
072
073            pauseRefreshButton.setSelected(!serialDCCppTC.isActiveRefresh());
074
075            refreshQueuedMessages();
076
077            add(serialPane, BorderLayout.PAGE_END);
078        }
079
080        // Create the background function refreshing-related buttons and add
081        // them to a panel. Will be hidden if not required by command station.
082        final JLabel functionLabel = new JLabel(Bundle.getMessage("LabelFunctionRefresh"), SwingConstants.LEFT); // NOI18N
083
084        pauseRefreshButton.setText(Bundle.getMessage("ButtonPauseRefresh")); // NOI18N
085        pauseRefreshButton.setVisible(true);
086        pauseRefreshButton.setToolTipText(Bundle.getMessage("TooltipPauseRefresh")); // NOI18N
087        // the selected state of pauseRefreshButton will be set when the context
088        // is created
089
090        clearRefreshQueueButton.setText(Bundle.getMessage("ButtonClearRefreshQueue")); // NOI18N
091        clearRefreshQueueButton.setVisible(true);
092        clearRefreshQueueButton.setToolTipText(Bundle.getMessage("TooltipClearRefreshQueue")); // NOI18N
093
094        serialPane.setLayout(new BoxLayout(serialPane, BoxLayout.LINE_AXIS));
095        serialPane.add(functionLabel);
096        serialPane.add(Box.createRigidArea(new Dimension(5, 0)));
097        serialPane.add(pauseRefreshButton);
098        serialPane.add(Box.createRigidArea(new Dimension(5, 0)));
099        serialPane.add(clearRefreshQueueButton);
100        serialPane.add(Box.createRigidArea(new Dimension(5, 0)));
101        serialPane.add(queuedEntriesLabel);
102
103        pauseRefreshButton.addActionListener((final java.awt.event.ActionEvent e) -> {
104            pauseButtonEvent(e);
105        });
106
107        clearRefreshQueueButton.addActionListener((final java.awt.event.ActionEvent e) -> {
108            clearButtonEvent(e);
109        });
110        
111        pack();        
112    }
113    
114    /**
115     * Define system-specific help item.
116     */
117//    @Override
118//    protected void setHelp() {
119//        addHelpMenu("package.jmri.jmrix.DCCpp.DCCpp.DCCppmon.DCCppMonFrame", true);  // NOI18N
120//    }
121    
122    //-------------------
123    //  Transmit Packets
124    //-------------------
125    @Override
126    public synchronized void message(DCCppMessage l) {
127
128        // display the decoded data
129        StringBuilder text = new StringBuilder();
130        if ( displayTranslatedCheckBox.isSelected() ) {
131            text.append(l.toMonitorString());
132        }
133        text.append("\n");
134
135        nextLine(text.toString(), (rawCheckBox.isSelected() ? l.toString() : ""), "TX:");
136        refreshQueuedMessages();
137
138    }
139
140    @Override
141    public void message(DCCppReply l) {
142        // receive a DCC-EX message and log it
143        // display the raw data if requested
144        if (log.isDebugEnabled()) {
145            log.debug("Message in Monitor: '{}' opcode {}", l, Character.toString(l.getOpCodeChar()));
146        }
147
148        StringBuilder text = new StringBuilder();
149        if ( displayTranslatedCheckBox.isSelected() ) {
150            text.append(l.toMonitorString());
151        }
152        text.append("\n");
153
154        nextLine(text.toString(), (rawCheckBox.isSelected() ? l.toString() : ""), "RX:");
155
156        //enable or disable the refresh pane based on support by command station
157        if (l.isStatusReply()) { 
158            if (tc.getCommandStation().isFunctionRefreshRequired()) { 
159                serialPane.setVisible(true);
160            } else {
161                serialPane.setVisible(false);
162            }
163        }
164       
165    }
166
167    @Override
168    public void notifyTimeout(DCCppMessage msg) {
169        // TODO Auto-generated method stub
170        
171    }
172
173    private void clearButtonEvent(final ActionEvent e) {
174        if (serialDCCppTC != null)
175            serialDCCppTC.clearRefreshQueue();
176
177        refreshQueuedMessages();
178    }
179    
180    private void pauseButtonEvent(final ActionEvent e) {
181        final JToggleButton source = (JToggleButton) e.getSource();
182
183        if (serialDCCppTC != null)
184            serialDCCppTC.setActiveRefresh(!source.isSelected());
185    }
186
187    private int previouslyQueuedMessages = -1;
188
189    public synchronized void refreshQueuedMessages() {
190        if (serialDCCppTC != null) {
191            final int currentlyQueuedMessages = serialDCCppTC.getQueueLength();
192
193            if (currentlyQueuedMessages != previouslyQueuedMessages) {
194                queuedEntriesLabel.setText(Bundle.getMessage("LabelQueuedEntries", String.valueOf(currentlyQueuedMessages))); // NOI18N
195
196                clearRefreshQueueButton.setEnabled(currentlyQueuedMessages > 0);
197
198                previouslyQueuedMessages = currentlyQueuedMessages;
199            }
200        }
201    }
202
203    private void displayTranslatedEvent(final ActionEvent e) {
204        if ( neitherCheckBoxSelected() ) {
205            rawCheckBox.setSelected(true);
206        }
207    }
208
209    private void rawCheckBoxEvent(final ActionEvent e) {
210        if ( neitherCheckBoxSelected() ) {
211            displayTranslatedCheckBox.setSelected(true);
212        }
213    }
214
215    private boolean neitherCheckBoxSelected() {
216        return (!( displayTranslatedCheckBox.isSelected()) ) && (!(rawCheckBox.isSelected()));
217    }
218
219    @Override
220    protected JPanel getActionButtonsPanel() {
221        // Combine action + log buttons into one wrapping row so they stay grouped.
222        super.getActionButtonsPanel(); // wire listeners/tooltips on the shared buttons
223        super.getLogToFilePanel();
224        JPanel p = new JPanel(new WrapLayout(FlowLayout.CENTER, 5, 0));
225        p.add(clearButton);
226        p.add(freezeButton);
227        p.add(openFileChooserButton);
228        p.add(startLogButton);
229        p.add(stopLogButton);
230        return p;
231    }
232
233    @Override
234    protected JPanel getLogToFilePanel() {
235        return new JPanel(); // log buttons live in getActionButtonsPanel
236    }
237
238    @Override
239    public JPanel getCheckBoxPanel(){
240        super.getCheckBoxPanel(); // wire listeners/tooltips on the shared checkboxes
241        displayTranslatedCheckBox.addActionListener(this::displayTranslatedEvent);
242        rawCheckBox.addActionListener(this::rawCheckBoxEvent);
243        displayTranslatedCheckBox.setSelected(!userPrefs.getSimplePreferenceState(doNotDisplayTranslatedCheck));
244        rawCheckBoxEvent(null); // if neither raw nor translated selected, force translated on.
245
246        JPanel p = new JPanel(new WrapLayout(FlowLayout.CENTER, 5, 0));
247        p.add(displayTranslatedCheckBox);
248        p.add(rawCheckBox);
249        p.add(timeCheckBox);
250        p.add(alwaysOnTopCheckBox);
251        p.add(autoScrollCheckBox);
252        return p;
253    }
254
255    @Override
256    protected boolean useStackedControlsLayout() {
257        return true;
258    }
259
260    @Override
261    public void initComponents() {
262        super.initComponents();
263        // BoxLayout's first pass after a resize computes child preferred-heights
264        // with the stale widths, so WrapLayout rows can be one pass out of date.
265        // Defer a second revalidate to settle layout using the new widths.
266        addComponentListener(new ComponentAdapter() {
267            @Override
268            public void componentResized(ComponentEvent e) {
269                SwingUtilities.invokeLater(DCCppMonFrame.this::revalidate);
270            }
271        });
272    }
273
274    @Override
275    public void dispose() {
276        if ( tc != null ) {
277            tc.removeDCCppListener(~0, this);
278        }
279        if (userPrefs!=null) {
280            userPrefs.setSimplePreferenceState(doNotDisplayTranslatedCheck, !displayTranslatedCheckBox.isSelected());
281        }
282        super.dispose();
283    }
284
285    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DCCppMonFrame.class);
286
287}