001package jmri.jmrit.beantable.signalmast;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.event.ActionEvent;
006import java.beans.PropertyChangeListener;
007import java.util.ArrayList;
008import java.util.HashSet;
009import java.util.Set;
010
011import javax.swing.BorderFactory;
012import javax.swing.BoxLayout;
013import javax.swing.JButton;
014import javax.swing.JLabel;
015import javax.swing.JPanel;
016import javax.swing.JScrollPane;
017import javax.swing.JTable;
018import javax.swing.JTextField;
019import javax.swing.SortOrder;
020import javax.swing.border.TitledBorder;
021import javax.swing.table.AbstractTableModel;
022import javax.swing.table.TableCellEditor;
023import javax.swing.table.TableRowSorter;
024
025import jmri.InstanceManager;
026import jmri.JmriException;
027import jmri.SignalMast;
028import jmri.SignalMastManager;
029import jmri.implementation.SignalMastRepeater;
030import jmri.managers.DefaultSignalMastManager;
031import jmri.swing.NamedBeanComboBox;
032import jmri.swing.RowSorterUtil;
033import jmri.util.swing.JComboBoxUtil;
034import jmri.util.swing.JmriPanel;
035import jmri.util.swing.JmriJOptionPane;
036import jmri.util.table.ButtonEditor;
037import jmri.util.table.ButtonRenderer;
038
039/**
040 * Frame for Signal Mast Add / Edit Panel
041 *
042 * @author Kevin Dickerson Copyright (C) 2011
043 */
044public class SignalMastRepeaterPanel extends JmriPanel {
045
046    final DefaultSignalMastManager dsmm;
047    private ArrayList<SignalMastRepeater> _signalMastRepeaterList;
048    private SignalMastRepeaterModel _RepeaterModel;
049    private NamedBeanComboBox<SignalMast> _MasterBox;
050    private NamedBeanComboBox<SignalMast> _SlaveBox;
051    private JButton _addRepeater;
052
053    public SignalMastRepeaterPanel() {
054        super();
055        dsmm = (DefaultSignalMastManager) InstanceManager.getDefault(SignalMastManager.class);
056        init();
057    }
058
059    final void init() {
060
061        setLayout(new BorderLayout());
062
063        JPanel header = new JPanel();
064        header.setLayout(new BoxLayout(header, BoxLayout.Y_AXIS));
065
066        JPanel sourcePanel = new JPanel();
067
068        header.add(sourcePanel);
069        add(header, BorderLayout.NORTH);
070
071        _RepeaterModel = new SignalMastRepeaterModel();
072        JTable repeaterTable = new JTable(_RepeaterModel);
073
074        TableRowSorter<SignalMastRepeaterModel> sorter = new TableRowSorter<>(_RepeaterModel); // leave default sorting
075        RowSorterUtil.setSortOrder(sorter, SignalMastRepeaterModel.DIR_COLUMN, SortOrder.ASCENDING);
076        repeaterTable.setRowSorter(sorter);
077
078        repeaterTable.setRowSelectionAllowed(false);
079        repeaterTable.setPreferredScrollableViewportSize(new java.awt.Dimension(526, 120));
080        _RepeaterModel.configureTable(repeaterTable);
081        
082        JScrollPane signalAppearanceScrollPane = new JScrollPane(repeaterTable);
083        _RepeaterModel.fireTableDataChanged();
084        add(signalAppearanceScrollPane, BorderLayout.CENTER);
085
086        JPanel footer = new JPanel();
087        updateDetails();
088
089        _MasterBox = new NamedBeanComboBox<>(dsmm);
090        _MasterBox.addActionListener( e -> setSlaveBoxLists());
091        JComboBoxUtil.setupComboBoxMaxRows(_MasterBox);
092
093        _SlaveBox = new NamedBeanComboBox<>(dsmm);
094        JComboBoxUtil.setupComboBoxMaxRows(_SlaveBox);
095        _SlaveBox.setEnabled(false);
096        footer.add(new JLabel(Bundle.getMessage("Master") + " : "));
097        footer.add(_MasterBox);
098        footer.add(new JLabel(Bundle.getMessage("Slave") + " : "));
099        footer.add(_SlaveBox);
100        _addRepeater = new JButton(Bundle.getMessage("ButtonAddText"));
101        _addRepeater.setEnabled(false);
102        _addRepeater.addActionListener((ActionEvent e) -> {
103            SignalMastRepeater rp = new SignalMastRepeater(_MasterBox.getSelectedItem(), _SlaveBox.getSelectedItem());
104            try {
105                dsmm.addRepeater(rp);
106            } catch (JmriException ex) {
107                String error = java.text.MessageFormat.format(Bundle.getMessage("MessageAddFailed"),
108                    new Object[]{_MasterBox.getSelectedItemDisplayName(), _SlaveBox.getSelectedItemDisplayName()});
109                log.error("Failed to add Repeater. {} {}", error, ex.getMessage());
110                JmriJOptionPane.showMessageDialog(this, error,
111                    Bundle.getMessage("TitleAddFailed"), JmriJOptionPane.ERROR_MESSAGE);
112            }
113        });
114        footer.add(_addRepeater);
115
116        TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
117        border.setTitle(Bundle.getMessage("AddRepeater"));
118        footer.setBorder(border);
119
120        add(footer, BorderLayout.SOUTH);
121    }
122
123    void setSlaveBoxLists() {
124        SignalMast masterMast = _MasterBox.getSelectedItem();
125        if (masterMast == null) {
126            _SlaveBox.setEnabled(false);
127            _addRepeater.setEnabled(false);
128            return;
129        }
130        java.util.Iterator<SignalMast> iter
131                = dsmm.getNamedBeanSet().iterator();
132
133        // don't return an element if there are not sensors to include
134        if (!iter.hasNext()) {
135            return;
136        }
137        Set<SignalMast> excludedSignalMasts = new HashSet<>();
138        while (iter.hasNext()) {
139            SignalMast s = iter.next();
140            if ( ( s.getAppearanceMap() != masterMast.getAppearanceMap() )
141                    || ( s == masterMast ) ) {
142                excludedSignalMasts.add(s);
143            }
144        }
145        _SlaveBox.setExcludedItems(excludedSignalMasts);
146        if (excludedSignalMasts.size() == dsmm.getNamedBeanSet().size()) {
147            _SlaveBox.setEnabled(false);
148            _addRepeater.setEnabled(false);
149        } else {
150            _SlaveBox.setEnabled(true);
151            _addRepeater.setEnabled(true);
152        }
153    }
154
155    private void updateDetails() {
156        _signalMastRepeaterList = new ArrayList<>(dsmm.getRepeaterList());
157        _RepeaterModel.fireTableDataChanged();//updateSignalMastLogic(old, sml);
158    }
159
160    @Override
161    public void dispose() {
162        _RepeaterModel.dispose();
163        super.dispose();
164    }
165
166    private class SignalMastRepeaterModel extends AbstractTableModel implements PropertyChangeListener {
167
168        SignalMastRepeaterModel() {
169            super();
170            init();
171        }
172
173        final void init(){
174            dsmm.addPropertyChangeListener(SignalMastRepeaterModel.this);
175        }
176
177        @Override
178        public Class<?> getColumnClass(int c) {
179            switch (c) {
180                case DIR_COLUMN:
181                case DEL_COLUMN:
182                    return JButton.class;
183                case ENABLE_COLUMN:
184                    return Boolean.class;
185                default:
186                    return String.class;
187            }
188        }
189
190        public void configureTable(JTable table) {
191            // allow reordering of the columns
192            table.getTableHeader().setReorderingAllowed(true);
193
194            // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
195            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
196
197            // resize columns as requested
198            for (int i = 0; i < table.getColumnCount(); i++) {
199                int width = getPreferredWidth(i);
200                table.getColumnModel().getColumn(i).setPreferredWidth(width);
201            }
202            table.sizeColumnsToFit(-1);
203
204            configEditColumn(table);
205
206        }
207
208        @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES",
209                                justification="better to keep cases in column order rather than to combine")
210        public int getPreferredWidth(int col) {
211            switch (col) {
212                case ENABLE_COLUMN:
213                case DIR_COLUMN:
214                    return new JTextField(5).getPreferredSize().width;
215                case SLAVE_COLUMN:
216                    return new JTextField(15).getPreferredSize().width;
217                case MASTER_COLUMN:
218                    return new JTextField(15).getPreferredSize().width;
219                case DEL_COLUMN: // not actually used due to the configureTable, setColumnToHoldButton, configureButton
220                    return new JTextField(22).getPreferredSize().width;
221                default:
222                    log.warn("Unexpected column in getPreferredWidth: {}", col);
223                    return new JTextField(8).getPreferredSize().width;
224            }
225        }
226
227        @Override
228        public String getColumnName(int col) {
229            switch (col) {
230                case MASTER_COLUMN:
231                    return Bundle.getMessage("ColumnMaster");
232                case DIR_COLUMN:
233                    return Bundle.getMessage("ColumnDir");
234                case SLAVE_COLUMN:
235                    return Bundle.getMessage("ColumnSlave");
236                case ENABLE_COLUMN:
237                    return Bundle.getMessage("ColumnHeadEnabled");
238                case DEL_COLUMN:
239                default:
240                    return "";
241            }
242        }
243
244        private void dispose() {
245            dsmm.removePropertyChangeListener(SignalMastRepeaterModel.this);
246        }
247
248        @Override
249        public void propertyChange(java.beans.PropertyChangeEvent e) {
250            if ( SignalMastManager.PROPERTY_REPEATER_LENGTH.equals(e.getPropertyName())) {
251                updateDetails();
252            }
253        }
254
255        protected void configEditColumn(JTable table) {
256            // have the delete column hold a button
257            /*AbstractTableAction.Bundle.getMessage("EditDelete")*/
258
259            JButton b = new JButton("< >");
260            b.putClientProperty("JComponent.sizeVariant", "small");
261            b.putClientProperty("JButton.buttonType", "square");
262
263            setColumnToHoldButton(table, DIR_COLUMN,
264                    b);
265            setColumnToHoldButton(table, DEL_COLUMN,
266                    new JButton(Bundle.getMessage("ButtonDelete")));
267        }
268
269        protected void setColumnToHoldButton(JTable table, int column, JButton sample) {
270            //TableColumnModel tcm = table.getColumnModel();
271            // install a button renderer & editor
272            ButtonRenderer buttonRenderer = new ButtonRenderer();
273            table.setDefaultRenderer(JButton.class, buttonRenderer);
274            TableCellEditor buttonEditor = new ButtonEditor(new JButton());
275            table.setDefaultEditor(JButton.class, buttonEditor);
276            // ensure the table rows, columns have enough room for buttons
277            table.setRowHeight(sample.getPreferredSize().height);
278            table.getColumnModel().getColumn(column)
279                    .setPreferredWidth((sample.getPreferredSize().width) + 4);
280        }
281
282        @Override
283        public int getColumnCount() {
284            return 5;
285        }
286
287        @Override
288        public boolean isCellEditable(int r, int c) {
289            switch (c) {
290                case DEL_COLUMN:
291                case ENABLE_COLUMN:
292                case DIR_COLUMN:
293                    return true;
294                default:
295                    return false;
296            }
297        }
298
299        protected void deleteRepeater(int r) {
300            dsmm.removeRepeater(_signalMastRepeaterList.get(r));
301        }
302
303        public static final int MASTER_COLUMN = 0;
304        public static final int DIR_COLUMN = 1;
305        public static final int SLAVE_COLUMN = 2;
306        public static final int ENABLE_COLUMN = 3;
307        public static final int DEL_COLUMN = 4;
308
309        @Override
310        public int getRowCount() {
311            return ( _signalMastRepeaterList == null ? 0 : _signalMastRepeaterList.size() );
312        }
313
314        @Override
315        public Object getValueAt(int r, int c) {
316            if (r >= _signalMastRepeaterList.size()) {
317                log.debug("row is greater than turnout list size");
318                return null;
319            }
320            switch (c) {
321                case MASTER_COLUMN:
322                    return _signalMastRepeaterList.get(r).getMasterMastName();
323                case DIR_COLUMN:  // slot number
324                    return getValueAtDirectionCol(r);
325                case SLAVE_COLUMN:
326                    return _signalMastRepeaterList.get(r).getSlaveMastName();
327                case ENABLE_COLUMN:
328                    return _signalMastRepeaterList.get(r).getEnabled();
329                case DEL_COLUMN:
330                    return Bundle.getMessage("ButtonDelete");
331                default:
332                    return null;
333            }
334        }
335
336        private String getValueAtDirectionCol(int r) {
337            switch (_signalMastRepeaterList.get(r).getDirection()) {
338                case SignalMastRepeater.MASTERTOSLAVE:
339                    return " > ";
340                case SignalMastRepeater.SLAVETOMASTER:
341                    return " < ";
342                case SignalMastRepeater.BOTHWAY:
343                default:
344                    return "< >";
345            }
346        }
347
348        @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES",
349                                justification="better to keep cases in column order rather than to combine")
350        @Override
351        public void setValueAt(Object type, int r, int c) {
352            switch (c) {
353                case DIR_COLUMN:
354                    setValueAtDirectionCol(r);
355                    _RepeaterModel.fireTableDataChanged();
356                    break;
357                case DEL_COLUMN:
358                    deleteRepeater(r);
359                    break;
360                case ENABLE_COLUMN:
361                    boolean b = ((Boolean) type);
362                    _signalMastRepeaterList.get(r).setEnabled(b);
363                    break;
364                default:
365                    break;
366            }
367        }
368
369        private void setValueAtDirectionCol(int r) {
370            switch (_signalMastRepeaterList.get(r).getDirection()) {
371                case SignalMastRepeater.BOTHWAY:
372                    _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.MASTERTOSLAVE);
373                    break;
374                case SignalMastRepeater.MASTERTOSLAVE:
375                    _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.SLAVETOMASTER);
376                    break;
377                case SignalMastRepeater.SLAVETOMASTER:
378                default:
379                    _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.BOTHWAY);
380                    break;
381            }
382        }
383    }
384
385    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalMastRepeaterPanel.class);
386
387}