001package jmri.jmrit.cabsignals;
002
003import java.awt.BorderLayout;
004import java.awt.Component;
005import java.awt.Dimension;
006import java.awt.event.ActionEvent;
007import java.awt.event.ActionListener;
008import java.awt.event.KeyEvent;
009import java.awt.event.KeyListener;
010import java.beans.PropertyChangeEvent;
011import java.util.ArrayList;
012import java.util.Arrays;
013import java.util.List;
014
015import javax.swing.*;
016import javax.swing.event.ChangeEvent;
017import javax.swing.table.TableCellRenderer;
018import javax.swing.table.TableColumn;
019import javax.swing.table.TableRowSorter;
020import javax.swing.text.DefaultFormatter;
021
022import jmri.LocoAddress;
023import jmri.CabSignalListListener;
024import jmri.CabSignalManager;
025import jmri.jmrit.catalog.NamedIcon;
026import jmri.jmrit.DccLocoAddressSelector;
027import jmri.jmrit.roster.swing.GlobalRosterEntryComboBox;
028import jmri.jmrit.roster.swing.RosterEntryComboBox;
029import jmri.util.swing.XTableColumnModel;
030import jmri.util.swing.StayOpenCheckBoxItem;
031import jmri.util.swing.JmriJOptionPane;
032import jmri.util.table.ButtonEditor;
033import jmri.util.table.ButtonRenderer;
034import jmri.util.table.JTableWithColumnToolTips;
035
036/**
037 * Pane for sending Cab Signal data via block lookup
038 * @author Steve Young Copyright (C) 2018
039 * @author Paul Bender Copyright (C) 2019
040 * @see CabSignalTableModel
041 * @since 4.13.4
042 */
043public class CabSignalPane extends jmri.util.swing.JmriPanel implements CabSignalListListener {
044
045    private CabSignalManager cabSignalManager;
046
047    private JScrollPane slotScroll;
048    private CabSignalTableModel slotModel;
049    private JTable _slotTable;
050    private XTableColumnModel tcm;
051
052    private JMenu cabSigColMenu;
053    private List<JCheckBoxMenuItem> colMenuList;
054    private JToggleButton masterPauseButton;
055    private JLabel textLocoLabel;
056    private DccLocoAddressSelector locoSelector;
057    private RosterEntryComboBox locoRosterBox;
058    private JButton addLocoButton;
059    private JButton resetLocoButton;
060    private int _rotationOffset;
061    private int _defaultRowHeight;
062    
063    public CabSignalPane() {
064        super();
065        cabSignalManager = jmri.InstanceManager.getNullableDefault(CabSignalManager.class);
066        if(cabSignalManager == null){
067           log.info("creating new DefaultCabSignalManager");
068           jmri.InstanceManager.store(new jmri.managers.DefaultCabSignalManager(),CabSignalManager.class);
069           cabSignalManager = jmri.InstanceManager.getNullableDefault(CabSignalManager.class); 
070        }
071    }
072    
073    /**
074     * {@inheritDoc}
075     */
076    @Override
077    public void initComponents() {
078        super.initComponents();
079        if (cabSignalManager != null) {
080            cabSignalManager.addCabSignalListListener(this);
081        }
082        slotModel = new CabSignalTableModel(5,
083            CabSignalTableModel.MAX_COLUMN); // row, column
084        
085        tcm = new XTableColumnModel();
086        cabSigColMenu = new JMenu(Bundle.getMessage("SigDataCol"));
087        colMenuList = new ArrayList<>();
088        textLocoLabel = new JLabel();
089        locoSelector = new DccLocoAddressSelector();
090        addLocoButton = new JButton();
091        resetLocoButton = new JButton();
092        _defaultRowHeight = 26;
093        init();
094    }
095
096    public void init() {
097        _slotTable = new JTableWithColumnToolTips(slotModel,CabSignalTableModel.COLUMNTOOLTIPS);        
098        
099        // Use XTableColumnModel so we can control which columns are visible
100        _slotTable.setColumnModel(tcm);
101        _slotTable.createDefaultColumnsFromModel();
102        
103        for (int i = 0; i < _slotTable.getColumnCount(); i++) {
104            int colnumber=i;
105            String colName = _slotTable.getColumnName(colnumber);
106            StayOpenCheckBoxItem showcol = new StayOpenCheckBoxItem(colName);
107            showcol.setToolTipText(CabSignalTableModel.COLUMNTOOLTIPS[i]);
108            colMenuList.add(showcol);
109            cabSigColMenu.add(showcol); // cabsig columns
110        }
111
112        for (int i = 0; i < CabSignalTableModel.MAX_COLUMN; i++) {
113            int colnumber=i;
114                TableColumn column  = tcm.getColumnByModelIndex(colnumber);
115                
116            if (Arrays.stream(CabSignalTableModel.STARTUPCOLUMNS).anyMatch(j -> j == colnumber)) {
117                colMenuList.get(colnumber).setSelected(true);
118                tcm.setColumnVisible(column, true);
119            } else {
120                colMenuList.get(colnumber).setSelected(false);
121                tcm.setColumnVisible(column, false);
122            }
123        
124            colMenuList.get(colnumber).addActionListener((ActionEvent e) -> {
125                TableColumn column1 = tcm.getColumnByModelIndex(colnumber);
126                boolean visible1 = tcm.isColumnVisible(column1);
127                tcm.setColumnVisible(column1, !visible1);
128            });
129        }
130        
131        _slotTable.setAutoCreateRowSorter(true);
132        
133        final TableRowSorter<CabSignalTableModel> sorter = new TableRowSorter<>(slotModel);
134        _slotTable.setRowSorter(sorter);
135        
136        _slotTable.setRowHeight(_defaultRowHeight);
137        
138        // configure items for GUI
139        slotModel.configureTable(_slotTable);
140        
141        tcm.getColumnByModelIndex(CabSignalTableModel.REVERSE_BLOCK_DIR_BUTTON_COLUMN).setCellRenderer( 
142            new ButtonRenderer() );
143        tcm.getColumnByModelIndex(CabSignalTableModel.REVERSE_BLOCK_DIR_BUTTON_COLUMN).setCellEditor(
144            new ButtonEditor( new JButton() ) );
145            
146        tcm.getColumnByModelIndex(CabSignalTableModel.NEXT_ASPECT_ICON).setCellRenderer( 
147            tableSignalAspectRenderer() ); 
148        
149        slotScroll = new JScrollPane(_slotTable);
150        slotScroll.setPreferredSize(new Dimension(400, 200));
151        
152        this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
153
154        // add event displays
155        JPanel p1 = new JPanel();
156        p1.setLayout(new BorderLayout());
157        
158        JPanel toppanelcontainer = new JPanel();
159        // toppanelcontainer.setLayout(new BoxLayout(toppanelcontainer, BoxLayout.X_AXIS));
160        
161        masterPauseButton= new JToggleButton();
162        masterPauseButton.setSelected(false); // cabdata on
163        refreshMasterPauseButton();
164        masterPauseButton.setVisible(true);
165        masterPauseButton.addActionListener ((ActionEvent e) -> {
166            refreshMasterPauseButton();
167        });
168        
169        toppanelcontainer.add(masterPauseButton);
170        
171        JPanel locoSelectContainer = new JPanel();
172
173        textLocoLabel.setText(Bundle.getMessage("LocoLabelText"));
174        textLocoLabel.setVisible(true);
175
176        locoSelector.setToolTipText(Bundle.getMessage("LocoSelectorToolTip"));
177        locoSelector.setVisible(true);
178        textLocoLabel.setLabelFor(locoSelector);
179
180        locoSelectContainer.add(textLocoLabel);
181        locoSelectContainer.add(locoSelector);
182
183        locoSelector.addKeyListener(new KeyListener() {
184            @Override
185            public void keyPressed(KeyEvent e) {
186                // if we start typing, set the selected index of the locoRosterbox to nothing.
187                locoRosterBox.setSelectedIndex(0);
188            }
189
190            @Override
191            public void keyTyped(KeyEvent e) {
192            }
193
194            @Override
195            public void keyReleased(KeyEvent e) {
196            }
197        });
198
199        locoRosterBox = new GlobalRosterEntryComboBox();
200        locoRosterBox.setNonSelectedItem("");
201        locoRosterBox.setSelectedIndex(0);
202
203        locoRosterBox.addPropertyChangeListener("selectedRosterEntries", (PropertyChangeEvent pce) -> {
204            locoSelected();
205        });
206        locoRosterBox.setVisible(true);
207        locoSelectContainer.add(locoRosterBox);
208
209        addLocoButton.setText(Bundle.getMessage("ButtonAddText"));
210        addLocoButton.setVisible(true);
211        addLocoButton.setToolTipText(Bundle.getMessage("AddButtonToolTip"));
212        addLocoButton.addActionListener((ActionEvent e) -> {
213            addLocoButtonActionPerformed(e);
214        });
215        locoSelectContainer.add(addLocoButton);
216
217        resetLocoButton.setText(Bundle.getMessage("ButtonReset"));
218        resetLocoButton.setVisible(true);
219        resetLocoButton.setToolTipText(Bundle.getMessage("ResetButtonToolTip"));
220        resetLocoButton.addActionListener((ActionEvent e) -> {
221            locoSelector.reset();
222            locoRosterBox.setSelectedIndex(0);
223        });
224
225        locoSelectContainer.add(resetLocoButton);
226        locoSelectContainer.setBorder(javax.swing.BorderFactory.createEtchedBorder());
227        
228        locoSelectContainer.setVisible(true);
229        toppanelcontainer.add(locoSelectContainer);
230
231        p1.add(toppanelcontainer, BorderLayout.PAGE_START);
232        p1.add(slotScroll, BorderLayout.CENTER);        
233        add(p1);
234        
235        Dimension p1size = new Dimension(450, 200);
236        p1.setMinimumSize(p1size);
237        
238        p1.setVisible(true);
239        log.debug("class name {} ",CabSignalPane.class.getName());
240    }
241    
242    private void refreshMasterPauseButton(){
243        if (masterPauseButton.isSelected()) { // is paused
244            masterPauseButton.setText(Bundle.getMessage("SigDataResume"));
245            masterPauseButton.setToolTipText(Bundle.getMessage("SigDataResumeTip"));
246            slotModel.setPanelPauseButton( true );
247        }
248        else { // pause relased, go back to normal
249            masterPauseButton.setText(Bundle.getMessage("SigDataPause"));
250            masterPauseButton.setToolTipText(Bundle.getMessage("SigDataPauseTip"));
251            slotModel.setPanelPauseButton( false );
252        }
253    }
254    
255    /**
256     * {@inheritDoc}
257     */
258    @Override
259    public String getTitle() {
260        return Bundle.getMessage("CabSignalPaneTitle");
261    }
262    
263    /**
264     * {@inheritDoc}
265     */
266    @Override
267    public List<JMenu> getMenus() {
268        List<JMenu> menuList = new ArrayList<>();
269        
270        menuList.add(cabSigColMenu);
271        
272        JMenu displayMenu = new JMenu(Bundle.getMessage("DisplayMenu"));
273        
274        JMenu iconMenu = new JMenu(Bundle.getMessage("AspectIconMenu"));
275        ButtonGroup offsetGroup = new ButtonGroup();
276        
277        JRadioButtonMenuItem offset0MenuItem = new JRadioButtonMenuItem(Bundle.getMessage("IconDegrees", 0));
278        JRadioButtonMenuItem offset1MenuItem = new JRadioButtonMenuItem(Bundle.getMessage("IconDegrees", 90));
279        JRadioButtonMenuItem offset2MenuItem = new JRadioButtonMenuItem(Bundle.getMessage("IconDegrees", 180));
280        JRadioButtonMenuItem offset3MenuItem = new JRadioButtonMenuItem(Bundle.getMessage("IconDegrees", 270));
281        
282        offsetGroup.add(offset0MenuItem);
283        offsetGroup.add(offset1MenuItem);
284        offsetGroup.add(offset2MenuItem);
285        offsetGroup.add(offset3MenuItem);
286        
287        iconMenu.add(offset0MenuItem);
288        iconMenu.add(offset1MenuItem);
289        iconMenu.add(offset2MenuItem);
290        iconMenu.add(offset3MenuItem);
291        
292        displayMenu.add(iconMenu);
293        
294        _rotationOffset = 0; // startup
295        offset0MenuItem.setSelected(true);
296        ActionListener iconMenuListener = ae -> {
297            if ( offset0MenuItem.isSelected() ) {
298                _rotationOffset = 0;
299            }
300            else if ( offset1MenuItem.isSelected() ) {
301                _rotationOffset = 1;
302            }
303            else if ( offset2MenuItem.isSelected() ) {
304                _rotationOffset = 2;
305            }
306            else if ( offset3MenuItem.isSelected() ) {
307                _rotationOffset = 3;
308            }
309            notifyCabSignalListChanged();
310        };
311        offset0MenuItem.addActionListener(iconMenuListener);
312        offset1MenuItem.addActionListener(iconMenuListener);
313        offset2MenuItem.addActionListener(iconMenuListener);
314        offset3MenuItem.addActionListener(iconMenuListener);
315        
316        ActionListener rowHeightMenuListener = ae -> {
317            JSpinner delaySpinner = getNewRowHeightSpinner();
318            int option = JmriJOptionPane.showOptionDialog(this, 
319                delaySpinner, 
320                Bundle.getMessage("RowHeightOption"), 
321                JmriJOptionPane.OK_CANCEL_OPTION, 
322                JmriJOptionPane.QUESTION_MESSAGE, null, null, null);
323            if (option == JmriJOptionPane.OK_OPTION) {
324                _defaultRowHeight = (Integer) delaySpinner.getValue();
325            }
326            else {
327                _slotTable.setRowHeight(_defaultRowHeight);
328            }
329        };
330
331        JMenuItem searchForNodesMenuItem = new JMenuItem(Bundle.getMessage("RowHeightOption"));
332        searchForNodesMenuItem.addActionListener(rowHeightMenuListener);
333        displayMenu.add(searchForNodesMenuItem);
334        
335        menuList.add(displayMenu);
336        
337        return menuList;
338    }
339    
340    private JSpinner getNewRowHeightSpinner() {
341        JSpinner rqnnSpinner = new JSpinner(new SpinnerNumberModel(_defaultRowHeight, 10, 150, 1));
342        JComponent rqcomp = rqnnSpinner.getEditor();
343        JFormattedTextField rqfield = (JFormattedTextField) rqcomp.getComponent(0);
344        DefaultFormatter rqformatter = (DefaultFormatter) rqfield.getFormatter();
345        rqformatter.setCommitsOnValidEdit(true);
346        rqnnSpinner.addChangeListener((ChangeEvent e) -> {
347            _slotTable.setRowHeight((Integer) rqnnSpinner.getValue());
348        });
349        return rqnnSpinner;
350    }
351
352    public void addLocoButtonActionPerformed(ActionEvent e) {
353        if (locoSelector.getAddress() == null) {
354            return;
355        }
356        LocoAddress locoaddress = locoSelector.getAddress();
357        // create and inform CabSignal state of master pause / resume
358        cabSignalManager.getCabSignal(locoaddress).setMasterCabSigPauseActive( masterPauseButton.isSelected() );
359    }
360
361    public void locoSelected() {
362        if (locoRosterBox.getSelectedRosterEntries().length == 1) {
363            locoSelector.setAddress(locoRosterBox.getSelectedRosterEntries()[0].getDccLocoAddress());
364        }
365    }
366    
367    private TableCellRenderer tableSignalAspectRenderer() {
368    
369        return new TableCellRenderer() {
370            JLabel f = new JLabel();
371            /**
372             * {@inheritDoc}
373             */
374            @Override
375            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
376                boolean hasFocus, int row, int column) {
377                f.setIcon(null);
378                if ( !value.toString().isEmpty() ) {
379                    // value gets passed as a string so image can be rotated here
380                    NamedIcon tmpIcon = new NamedIcon(value.toString(), value.toString() );
381                    tmpIcon.setRotation( tmpIcon.getRotation() + _rotationOffset,slotScroll);
382                    //  double d = mastIcon.reduceTo(28, 28, 0.01d);
383                    f.setIcon(tmpIcon);
384                }
385                f.setText("");
386                f.setHorizontalAlignment(JLabel.CENTER);
387                if (isSelected) {
388                    f.setBackground( table.getSelectionBackground() );
389                } else {
390                    f.setBackground(null);
391                }
392                return f;
393            }
394        };
395    }
396    
397    /**
398     * {@inheritDoc}
399     */
400    @Override
401    public String getHelpTarget() {
402        return "package.jmri.jmrit.cabsignals.CabSignalPane";
403    }    
404    
405    /**
406     * {@inheritDoc}
407     */
408    @Override
409    public void dispose() {
410        cabSignalManager.removeCabSignalListListener(this);
411        _slotTable = null;
412        slotModel.dispose();
413        cabSignalManager = null;
414        super.dispose();
415    }
416
417    /**
418     * {@inheritDoc}
419     * Cab Signal List Listener interface
420     */
421    @Override
422    public void notifyCabSignalListChanged(){
423        slotModel.fireTableDataChanged();
424    }
425
426    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CabSignalPane.class);
427
428}